Agent Skills: 12-Factor App Modernization

|

UncategorizedID: plurigrid/asi/12-factor-app-modernization

Install this agent skill to your local

pnpm dlx add-skill https://github.com/plurigrid/asi/tree/HEAD/skills/12-factor-app-modernization

Skill Files

Browse the full folder contents for 12-factor-app-modernization.

Download Skill

Loading file tree…

skills/12-factor-app-modernization/SKILL.md

Skill Metadata

Name
12-factor-app-modernization
Description
|

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

| 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

  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