Asi 12-factor-app-modernization

install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/12-factor-app-modernization/SKILL.md
source content

12-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

FactorPrincipleKey Questions
I. CodebaseOne codebase, many deploysIs there a single repo? Are there environment-specific branches?
II. DependenciesExplicitly declare and isolateAre all dependencies declared with pinned versions?
III. ConfigStore config in environmentAre connection strings, credentials, and settings externalized?
IV. Backing ServicesTreat as attached resourcesCan backing services be swapped without code changes?
V. Build, Release, RunStrict separationAre build artifacts immutable? Is config injected at runtime?
VI. ProcessesStateless processesIs session state stored externally?
VII. Port BindingExport via port bindingAre services self-contained with embedded servers?
VIII. ConcurrencyScale via process modelCan the app scale horizontally?
IX. DisposabilityFast startup, graceful shutdownDoes the app handle SIGTERM? Is there an init system (tini)?
X. Dev/Prod ParityKeep environments similarAre dev and prod using same backing services?
XI. LogsTreat as event streamsDoes the app write to stdout/stderr?
XII. Admin ProcessesRun as one-off processesAre 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

  1. Hardcoded connection strings - Most common and most critical
  2. Unpinned dependencies - Causes "works on my machine" issues
  3. Missing init systems - Causes zombie processes and slow shutdowns
  4. Credentials in source control - Security vulnerability
  5. Environment-specific code branches - Violates codebase principle

Environment Variable Naming Conventions

Use consistent, descriptive names:

  • DB_HOST
    ,
    DB_PORT
    ,
    DB_USER
    ,
    DB_PASSWORD
    ,
    DB_NAME
  • REDIS_HOST
    ,
    REDIS_PORT
  • DATABASE_URL
    (for connection string format)
  • <SERVICE>_URL
    for full connection strings

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