Search

PythonReactSEOPDF
GitHub Skill

cloud-native-knowledge

Cloud Native knowledge base. Provides 12-Factor App principles with PHP-specific implementations, container-first patterns, and environment configuration for architecture audits.

dykyi-roman☆ 39 Repository stars5 Repository forks

Installation

Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.

View source ↗Guides

Skill instructions

Cloud Native Knowledge Base

Quick reference for 12-Factor App principles with PHP-specific implementations and container-first patterns.

12-Factor App Overview

┌─────────────────────────────────────────────────────────────────────┐
│                     12-FACTOR APP FLOW                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│   │ Codebase │───▶│   Build  │───▶│ Release  │───▶│   Run    │     │
│   │ (Git)    │    │ (Docker) │    │ (Tag)    │    │ (K8s)    │     │
│   └──────────┘    └──────────┘    └──────────┘    └──────────┘     │
│        │                │               │               │           │
│        │                │               │               │           │
│   ┌────▼─────┐    ┌────▼─────┐    ┌────▼─────┐    ┌────▼─────┐    │
│   │ composer │    │  ENV     │    │ Backing  │    │ Stateless│    │
│   │ .lock    │    │ Config   │    │ Services │    │ Processes│    │
│   │ (Deps)   │    │ (No .env │    │ (Redis,  │    │ (PHP-FPM │    │
│   │          │    │  in prod)│    │  DB, MQ) │    │  shared  │    │
│   └──────────┘    └──────────┘    └──────────┘    │  nothing)│    │
│                                                    └──────────┘    │
│                                                                      │
│   ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐     │
│   │ Port     │    │ Concur-  │    │ Dispos-  │    │ Dev/Prod │     │
│   │ Binding  │    │ rency    │    │ ability  │    │ Parity   │     │
│   │ (FPM,   │    │ (FPM     │    │ (Fast    │    │ (Docker  │     │
│   │  Runner) │    │  workers)│    │  start/  │    │  Compose)│     │
│   └──────────┘    └──────────┘    │  stop)   │    └──────────┘     │
│                                    └──────────┘                      │
│   ┌──────────┐    ┌──────────┐                                      │
│   │ Logs     │    │ Admin    │                                      │
│   │ (stderr  │    │ Processes│                                      │
│   │  stream) │    │ (Console │                                      │
│   └──────────┘    │  cmds)   │                                      │
│                    └──────────┘                                      │
└─────────────────────────────────────────────────────────────────────┘

The 12 Factors

# Factor PHP Implementation Anti-Pattern
1 Codebase One Git repo per service Multiple services in one repo (unless monorepo with clear boundaries)
2 Dependencies composer.lock committed, no global installs System-level pecl install without Dockerfile
3 Config $_ENV, getenv(), DotEnv (dev only) Hardcoded DB credentials, config in code
4 Backing services DSN via env vars, adapter pattern Direct new Redis('localhost')
5 Build, release, run Docker multi-stage build composer install in production at runtime
6 Processes Stateless PHP-FPM, shared-nothing $_SESSION file storage, local temp files
7 Port binding PHP built-in server, FrankenPHP, RoadRunner Apache module (mod_php) requiring full server
8 Concurrency PHP-FPM process model, horizontal scaling Single monolithic process
9 Disposability Fast startup, pcntl_signal for SIGTERM Long bootstrap, no graceful shutdown
10 Dev/prod parity Docker Compose matches production SQLite in dev, PostgreSQL in prod
11 Logs Monolog StreamHandler('php://stderr') Writing to /var/log/app.log files
12 Admin processes Symfony Console, artisan, one-off containers SSH into server and run scripts

Factor Details with PHP Examples

Factor 3: Config — Environment Variables
<?php

declare(strict_types=1);

namespace Infrastructure\Config;

final readonly class DatabaseConfig
{
    public readonly string $host;
    public readonly int $port;
    public readonly string $name;
    public readonly string $user;
    public readonly string $password;

    public function __construct()
    {
        $this->host = $this->requireEnv('DB_HOST');
        $this->port = (int) $this->requireEnv('DB_PORT');
        $this->name = $this->requireEnv('DB_NAME');
        $this->user = $this->requireEnv('DB_USER');
        $this->password = $this->requireEnv('DB_PASSWORD');
    }

    public function dsn(): string
    {
        return sprintf(
            'pgsql:host=%s;port=%d;dbname=%s',
            $this->host,
            $this->port,
            $this->name,
        );
    }

    private function requireEnv(string $name): string
    {
        $value = $_ENV[$name] ?? getenv($name);

        if ($value === false || $value === '') {
            throw new \RuntimeException(sprintf(
                'Required environment variable "%s" is not set',
                $name,
            ));
        }

        return (string) $value;
    }
}
Factor 4: Backing Services — Adapter Pattern
<?php

declare(strict_types=1);

namespace Infrastructure\Cache;

final readonly class RedisCacheAdapter implements CacheInterface
{
    private \Redis $redis;

    public function __construct()
    {
        $this->redis = new \Redis();
        $this->redis->connect(
            (string) ($_ENV['REDIS_HOST'] ?? 'localhost'),
            (int) ($_ENV['REDIS_PORT'] ?? 6379),
        );

        $password = $_ENV['REDIS_PASSWORD'] ?? null;
        if ($password !== null && $password !== '') {
            $this->redis->auth($password);
        }
    }

    public function get(string $key): mixed
    {
        $value = $this->redis->get($key);

        return $value !== false ? unserialize($value) : null;
    }

    public function set(string $key, mixed $value, int $ttl = 3600): void
    {
        $this->redis->setex($key, $ttl, serialize($value));
    }

    public function delete(string $key): void
    {
        $this->redis->del($key);
    }
}
Factor 5: Build, Release, Run — Docker Multi-Stage
# Stage 1: Build (install dependencies, compile assets)
FROM php:8.4-cli AS builder

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app

COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --optimize-autoloader

COPY . .
RUN composer dump-autoload --optimize --classmap-authoritative

# Stage 2: Run (minimal production image)
FROM php:8.4-fpm-alpine AS runtime

RUN docker-php-ext-install pdo_pgsql opcache

COPY docker/php/php.ini /usr/local/etc/php/conf.d/app.ini
COPY docker/php/fpm-pool.conf /usr/local/etc/php-fpm.d/www.conf
COPY --from=builder /app /app

WORKDIR /app

EXPOSE 9000
CMD ["php-fpm"]
Factor 9: Disposability — Graceful Shutdown
<?php

declare(strict_types=1);

namespace Infrastructure\Worker;

final class GracefulWorker
{
    private bool $shouldStop = false;

    public function __construct(
        private readonly MessageConsumerInterface $consumer,
    ) {
        pcntl_signal(SIGTERM, [$this, 'handleSignal']);
        pcntl_signal(SIGINT, [$this, 'handleSignal']);
    }

    public function run(): void
    {
        while (!$this->shouldStop) {
            pcntl_signal_dispatch();

            $message = $this->consumer->consume(timeoutMs: 1000);

            if ($message !== null) {
                $this->process($message);
                $this->consumer->acknowledge($message);
            }
        }

        $this->consumer->disconnect();
    }

    public function handleSignal(int $signal): void
    {
        $this->shouldStop = true;
    }

    private function process(Message $message): void
    {
        // Process message — keep this fast and idempotent
    }
}
Factor 11: Logs — Stream to stderr
<?php

declare(strict_types=1);

namespace Infrastructure\Logging;

use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Log\LoggerInterface;

final readonly class LoggerFactory
{
    public static function create(string $channel = 'app'): LoggerInterface
    {
        $logger = new Logger($channel);

        // 12-Factor: treat logs as event streams to stderr
        // Container runtime (Docker/K8s) captures and forwards
        $logger->pushHandler(new StreamHandler('php://stderr', Logger::INFO));

        return $logger;
    }
}
Factor 12: Admin Processes — One-Off Containers
<?php

declare(strict_types=1);

namespace Infrastructure\Console;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(name: 'app:migrate', description: 'Run database migrations')]
final class MigrateCommand extends Command
{
    public function __construct(
        private readonly MigrationRunnerInterface $migrationRunner,
    ) {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $output->writeln('Running migrations...');

        $applied = $this->migrationRunner->run();

        $output->writeln(sprintf('Applied %d migrations.', $applied));

        return Command::SUCCESS;
    }
}

// Run as one-off container:
// docker run --rm myapp:latest php bin/console app:migrate

PHP Violations Table

Factor Violation Why It Breaks Fix
1 Multiple apps in one repo without clear boundaries Deploy coupling, version entanglement Split repos or use monorepo tooling
2 pecl install at runtime Non-reproducible builds Install extensions in Dockerfile
3 .env file in production Secrets in filesystem, not rotatable Use real env vars or secrets vault
3 define('DB_HOST', 'localhost') Config in code Use $_ENV['DB_HOST']
4 new Redis('10.0.0.5') hardcoded Cannot swap backing service Inject via env var DSN
5 composer install at deploy time Slow, network-dependent, non-reproducible Install at build stage
6 $_SESSION with file handler State tied to instance Redis session handler
6 file_put_contents('/tmp/data.json') State on local disk Redis or object storage
7 Apache mod_php only Cannot self-serve on a port FPM + nginx, or FrankenPHP
8 Single-process Swoole without scaling Cannot scale horizontally FPM workers or multi-process Swoole
9 30s bootstrap, no SIGTERM handler Slow deploys, dropped requests Preloading + signal handling
10 SQLite dev, PostgreSQL prod Behavior differences in queries Docker Compose with PostgreSQL
11 file_put_contents('/var/log/app.log') Logs not collected by runtime Monolog to php://stderr
12 SSH into server to run scripts Not auditable, not reproducible One-off container commands

Container-First PHP Patterns

Health Check Endpoint
<?php

declare(strict_types=1);

namespace Presentation\Api\Action;

final readonly class HealthAction
{
    public function __construct(
        private \PDO $pdo,
        private \Redis $redis,
    ) {}

    public function __invoke(): array
    {
        $checks = [];

        try {
            $this->pdo->query('SELECT 1');
            $checks['database'] = 'ok';
        } catch (\Throwable) {
            $checks['database'] = 'fail';
        }

        try {
            $this->redis->ping();
            $checks['redis'] = 'ok';
        } catch (\Throwable) {
            $checks['redis'] = 'fail';
        }

        $healthy = !in_array('fail', $checks, true);

        return [
            'status' => $healthy ? 'healthy' : 'unhealthy',
            'checks' => $checks,
        ];
    }
}
Readiness vs Liveness
┌─────────────────────────────────────────────────────────────────────┐
│                   CONTAINER PROBES                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   Liveness Probe (/healthz)                                         │
│   • "Is the process alive?"                                         │
│   • Checks: PHP-FPM responds, not deadlocked                       │
│   • Failure: container is RESTARTED                                 │
│   • Keep simple: just return 200                                    │
│                                                                      │
│   Readiness Probe (/readyz)                                         │
│   • "Can this instance serve traffic?"                              │
│   • Checks: DB reachable, Redis reachable, migrations applied      │
│   • Failure: instance removed from load balancer                    │
│   • More comprehensive: verify all backing services                 │
│                                                                      │
│   Startup Probe                                                     │
│   • "Has the app finished bootstrapping?"                           │
│   • Checks: OPcache warmed, preloading done                        │
│   • Failure: container not yet considered for liveness/readiness    │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Detection Patterns

# Factor 3: Config via env vars
Grep: "getenv\(|\\$_ENV\[|env\(" --glob "**/*.php"
Grep: "define\(.*HOST\|define\(.*PASSWORD\|define\(.*SECRET" --glob "**/*.php"
Glob: **/.env
Glob: **/.env.prod

# Factor 4: Backing service configuration
Grep: "REDIS_HOST|REDIS_URL|DATABASE_URL|RABBITMQ_URL" --glob "**/.env*"
Grep: "new Redis\(|new \\\\PDO\(" --glob "**/src/**/*.php"

# Factor 5: Build artifacts
Glob: **/Dockerfile
Grep: "composer install" --glob "**/Dockerfile"
Grep: "multi-stage|AS builder|AS runtime" --glob "**/Dockerfile"

# Factor 6: Stateless violations
Grep: "session\.save_handler.*files|session_start" --glob "**/*.php"
Grep: "file_put_contents|fwrite" --glob "**/src/**/*.php"

# Factor 9: Graceful shutdown
Grep: "pcntl_signal|SIGTERM|SIGINT" --glob "**/*.php"

# Factor 11: Log streaming
Grep: "php://stderr|php://stdout" --glob "**/*.php"
Grep: "StreamHandler|error_log" --glob "**/*.php"
Grep: "file_put_contents.*log|fopen.*log" --glob "**/*.php"

# Factor 12: Admin processes
Grep: "AsCommand|Command::class" --glob "**/*.php"
Glob: **/bin/console
Glob: **/artisan

# Container health checks
Grep: "HEALTHCHECK|healthz|readyz" --glob "**/Dockerfile"
Grep: "health_check|liveness|readiness" --glob "**/docker-compose*.yml"

Quick Reference

12-Factor Compliance Checklist
Factor Check Pass Criteria
Codebase Single repo per deployable git remote -v shows one origin
Dependencies Lock file committed composer.lock in Git
Config No secrets in code Zero define() with credentials
Backing services DSN from env All connections via $_ENV
Build/Release/Run Docker multi-stage Separate build and runtime stages
Processes Stateless No $_SESSION files, no local temp
Port binding Self-contained FPM or embedded server
Concurrency Process model pm = dynamic or static
Disposability Fast start/stop < 5s startup, SIGTERM handler
Dev/prod parity Same services Docker Compose mirrors production
Logs Stream to stderr Monolog php://stderr handler
Admin One-off containers Console commands, no SSH

References

For detailed information, load these reference files:

  • references/twelve-factors.md — Detailed explanation of each factor with PHP code examples, DotEnv setup, Symfony secrets vault, Docker CMD, signal handling, log streaming

Examples

Read the usage examples in the source repository.

View source ↗

Source history

Indexed on
2026-03-11
Index updated
2026-03-11
Branch
main

Index and sync dates describe this directory. View the source repository for code history.

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!