Asi 12-factor-app-modernization
git clone https://github.com/plurigrid/asi
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/12-factor-app-modernization" ~/.claude/skills/plurigrid-asi-12-factor-app-modernization && rm -rf "$T"
skills/12-factor-app-modernization/SKILL.md12-Factor App Modernization
Quick Start
Analyze Application
# Clone repository git clone <repo-url> cd <repo-name> # Create inventory of services # Document tech stack, deployment configs, backing services
Generate Compliance Report
Create a structured JSON report documenting compliance status for all 12 factors.
The 12 Factors
| Factor | Principle | Key Questions |
|---|---|---|
| I. Codebase | One codebase, many deploys | Is there a single repo? Are there environment-specific branches? |
| II. Dependencies | Explicitly declare and isolate | Are all dependencies declared with pinned versions? |
| III. Config | Store config in environment | Are connection strings, credentials, and settings externalized? |
| IV. Backing Services | Treat as attached resources | Can backing services be swapped without code changes? |
| V. Build, Release, Run | Strict separation | Are build artifacts immutable? Is config injected at runtime? |
| VI. Processes | Stateless processes | Is session state stored externally? |
| VII. Port Binding | Export via port binding | Are services self-contained with embedded servers? |
| VIII. Concurrency | Scale via process model | Can the app scale horizontally? |
| IX. Disposability | Fast startup, graceful shutdown | Does the app handle SIGTERM? Is there an init system (tini)? |
| X. Dev/Prod Parity | Keep environments similar | Are dev and prod using same backing services? |
| XI. Logs | Treat as event streams | Does the app write to stdout/stderr? |
| XII. Admin Processes | Run as one-off processes | Are migrations and admin tasks separate from the main app? |
Application Analysis
Step 1: Clone and Inventory
Map out the application architecture:
- Identify all services/microservices
- Document the tech stack for each service (language, framework, runtime)
- List all deployment configurations (Dockerfiles, docker-compose, Kubernetes manifests)
- Identify backing services (databases, caches, message queues)
Step 2: Analyze Against Each Factor
Systematically evaluate each service against all 12 factors. Create a structured report documenting compliance status.
Compliance Report Structure
{ "title": "12-Factor App Compliance Report", "repository": "<repo-url>", "analysis_date": "<date>", "summary": { "total_factors": 12, "services_analyzed": ["<service-list>"], "overall_compliance": "<status>" }, "factors": { "<factor_id>": { "name": "<factor-name>", "status": "<COMPLIANT|PARTIAL|NON-COMPLIANT>", "findings": ["<observations>"], "issues": [{"service": "", "file": "", "problem": "", "severity": "", "impact": ""}], "fixes": [{"service": "", "file": "", "action": ""}] } }, "priority_fixes": [{"priority": 1, "category": "", "description": "", "affected_files": []}] }
Configuration Externalization (Factor III - Highest Priority)
This is typically the most critical violation. Hardcoded credentials and connection strings are security risks.
Python (Flask/Django)
# Before (hardcoded) redis = Redis(host="redis", port=6379) # After (externalized) redis_host = os.getenv('REDIS_HOST', 'redis') redis_port = int(os.getenv('REDIS_PORT', '6379')) redis = Redis(host=redis_host, port=redis_port)
Node.js
// Before (hardcoded) const pool = new Pool({ connectionString: 'postgres://user:pass@db/mydb' }); // After (externalized) const pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgres://user:pass@db/mydb' });
C#/.NET
// Before (hardcoded) var conn = new NpgsqlConnection("Server=db;Username=postgres;Password=secret;"); // After (externalized) var host = Environment.GetEnvironmentVariable("DB_HOST") ?? "db"; var user = Environment.GetEnvironmentVariable("DB_USER") ?? "postgres"; var password = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? "postgres"; var connString = $"Server={host};Username={user};Password={password};";
Dependency Management (Factor II)
Unpinned dependencies cause reproducibility issues and potential security vulnerabilities.
Python (requirements.txt)
# Before Flask Redis gunicorn # After Flask==3.0.0 Redis==5.0.1 gunicorn==21.2.0
Node.js (package.json)
// Before - caret allows minor/patch updates "dependencies": { "express": "^4.18.2" } // After - exact versions "dependencies": { "express": "4.18.2" }
Important: After changing package.json versions, regenerate package-lock.json:
rm package-lock.json && npm install --package-lock-only
Signal Handling (Factor IX)
Containers need proper init systems to handle signals correctly for graceful shutdown.
Add tini to Dockerfiles
# Install tini RUN apt-get update && \ apt-get install -y --no-install-recommends tini && \ rm -rf /var/lib/apt/lists/* # Use tini as entrypoint ENTRYPOINT ["/usr/bin/tini", "--"] CMD ["your-app-command"]
Docker Compose Configuration
Replace hardcoded values with environment variable substitution:
services: app: environment: DATABASE_URL: "postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db/mydb" REDIS_HOST: redis db: environment: POSTGRES_USER: "${POSTGRES_USER:-postgres}" POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-postgres}"
Kubernetes Secrets
Never store credentials in plain text in Kubernetes manifests.
Create Secret Manifest
apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque stringData: POSTGRES_USER: postgres POSTGRES_PASSWORD: <generate-strong-password>
Reference in Deployments
env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: db-credentials key: POSTGRES_PASSWORD
Validation
Docker Compose Validation
docker compose config --quiet && echo "✓ Valid"
Kubernetes Manifest Validation
kubectl apply --dry-run=client -f k8s-specifications/
Build and Test
docker compose build docker compose up -d docker compose ps # Verify all services healthy
Common Anti-Patterns
- Hardcoded connection strings - Most common and most critical
- Unpinned dependencies - Causes "works on my machine" issues
- Missing init systems - Causes zombie processes and slow shutdowns
- Credentials in source control - Security vulnerability
- Environment-specific code branches - Violates codebase principle
Environment Variable Naming Conventions
Use consistent, descriptive names:
,DB_HOST
,DB_PORT
,DB_USER
,DB_PASSWORDDB_NAME
,REDIS_HOSTREDIS_PORT
(for connection string format)DATABASE_URL
for full connection strings<SERVICE>_URL
Backward Compatibility
Always provide sensible defaults when externalizing config:
# Maintains backward compatibility with existing deployments host = os.getenv('REDIS_HOST', 'redis') # Falls back to 'redis'
References
- The Twelve-Factor App - Original methodology
- Beyond the Twelve-Factor App - O'Reilly extended guide
- Kubernetes Secrets Best Practices
- Docker Init Systems - tini documentation
- OWASP Secrets Management