| name | docker |
| description | Use when full-stack DevOps pipeline — Docker Compose for local dev, Dockerfile optimization for production images, Kubernetes deployment for scale. Turn container ops into a service business. |
| domain | devops |
| author | oyi77 |
| license | Apache-2.0 |
| subdomain | devops |
| tags | ["devops","docker","compose","kubernetes","k8s","ci-cd","dockerfile","money-making"] |
| version | 1.0.0 |
DevOps Money Protocol — Compose, Optimize, Deploy
When Not to Use
- Simple or one-off tasks — if the task is straightforward, direct execution is faster than structured methodology.
- Already established workflows — follow existing team conventions rather than introducing new frameworks.
- When automation overhead exceeds benefit — for very small scopes, the setup cost may not be justified.
Dependencies
- Python 3.8+ or Node.js 18+
- Access to relevant APIs/services for your specific use case
- Basic understanding of the domain concepts
Commands
Money-Making Overview
| Service | Client Price | Your Time | ROI |
|---|
| Docker Compose setup (multi-service) | $500–$1,500 | 1–3 hrs | $300–$500/hr |
| Dockerfile audit & optimization | $300–$1,000 | 30 min–1 hr | $400–$1,000/hr |
| CI/CD pipeline with docker build | $800–$3,000 | 2–5 hrs | $300–$600/hr |
| K8s deployment (single service) | $1,000–$4,000 | 3–8 hrs | $300–$500/hr |
| Full infra (compose→Dockerfile→k8s) | $3,000–$10,000 | 6–15 hrs | $400–$700/hr |
Target clients: SaaS startups, agencies, e-commerce teams who have code but no container pipeline. They deploy via SSH and pray. You fix that.
Delivery model: Fixed-price per environment (dev/staging/prod) or hourly consulting at $200–$400/hr.
Combined Capabilities
| Capability | Entry Point | Optimization | Production |
|---|
| Docker Compose | Multi-service YAML | Volume mounts, health checks, networks | Production-ready profiles |
| Dockerfile | Working build | Multi-stage, layer caching, distroless | Slim image (<100 MB) |
| K8s Deployment | YAML manifests | HPA, resource limits, probes | GitOps (ArgoCD/Flux) |
Concrete Action Flow
Phase 1: Docker Compose — Rapid Local Dev (30 min)
version: "3.9"
x-logging: &default-logging
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
app:
build:
context: .
dockerfile: Dockerfile
target: development
ports:
- "${PORT:-3000}:3000"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://user:pass@db:5432/app
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
[, ]
[, , ]
"""compose-init.py — Scaffold docker-compose.yml for client projects."""
import sys, json
from pathlib import Path
TEMPLATES = {
"web+postgres": {
"services": ["app", "db"],
"file": "docker-compose.yml",
},
"web+postgres+redis": {
"services": ["app", "db", "redis"],
"file": "docker-compose.yml",
},
"api+mysql+redis": {
"services": ["api", "mysql", "redis"],
"file": "docker-compose.yml",
},
}
def scaffold_compose(project_type: str, output: str = "."):
"""Scaffold a docker-compose.yml for common stacks."""
if project_type not in TEMPLATES:
print(f"Available: {', '.join(TEMPLATES.keys())}")
sys.exit(1)
src = Path(__file__).parent / f"templates/{project_type}/docker-compose.yml"
dst = Path(output) / "docker-compose.yml"
if src.exists():
dst.write_text(src.read_text())
print(f"Wrote {dst}")
:
()
__name__ == :
scaffold_compose(sys.argv[], sys.argv[] (sys.argv) > )
Phase 2: Dockerfile Optimization (1 hr)
Key optimization rules:
- Multi-stage builds — Builder stage has dev tools, runtime stage has only binaries
- Layer ordering — Least-changing layers first (deps → config → code)
- Distroless base — No shell, no package manager, smaller attack surface
.dockerignore — Exclude node_modules, .git, .env, pycache
# Dockerfile — Multi-stage production build
# ---- Stage 1: Build ----
FROM node:20-alpine AS builder
WORKDIR /build
# Dependency layer (cached until lockfile changes)
COPY package.json package-lock.json* ./
RUN npm ci --only=production --ignore-scripts
# Source layer (cached until source changes)
COPY . .
RUN npm run build
# ---- Stage 2: Production ----
FROM node:20-alpine AS production
WORKDIR /app
# Add non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Copy only production artifacts
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
COPY --from=builder /build/package.json ./
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode !== 200 ? 1 : 0))" \
|| exit 1
CMD ["node", "dist/index.js"]
# Alternative: Python with pip
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt ./
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.12-slim AS production
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY --from=builder /build/requirements.txt ./
COPY src/ ./src
ENV PATH=/root/.local/bin:$PATH
RUN adduser --disabled-password --gecos "" appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
"""dockerfile-optimize.py — Analyze and suggest Dockerfile improvements."""
import re, sys
from pathlib import Path
def analyze_dockerfile(path: str) -> list[dict]:
"""Analyze a Dockerfile and return optimization suggestions."""
content = Path(path).read_text()
lines = content.split("\n")
findings = []
if content.count("FROM ") > 1:
stages = re.findall(r"FROM\s+\S+\s+AS\s+(\w+)", content, re.I)
if len(stages) >= 2:
findings.append({"severity": "info", "line": 0, "message": f"Multi-stage build with {len(stages)} stages: {', '.join(stages)}"})
else:
findings.append({"severity": "warn", "line": 0, "message": "Single-stage build. Use multi-stage to reduce final image size."})
for i, line in enumerate(lines, 1):
m = re.match(r"FROM\s+(\S+)", line)
if m:
img = m.group(1)
img img img img:
findings.append({: , : i, : })
content content:
findings.append({: , : , : })
content:
findings.append({: , : , : })
dockerignore = Path(path).parent /
dockerignore.exists():
findings.append({: , : , : })
findings
() -> :
() -> :
findings = analyze_dockerfile(path)
lines = [, , ]
errors = [f f findings findings[] == ]
warnings = [f f findings f[] == ]
infos = [f f findings f[] == ]
errors:
lines.append()
f errors:
lines.append()
warnings:
lines.append()
f warnings:
lines.append()
infos:
lines.append()
f infos:
lines.append()
findings:
lines.append()
.join(lines)
__name__ == :
(report_dockerfile(sys.argv[]))
Phase 3: Image Size Optimization
#!/usr/bin/env bash
set -euo pipefail
IMAGE="${1:?Usage: $0 image:tag}"
echo "=== Image layers for $IMAGE ==="
docker history --no-trunc --format "table {{.CreatedSince}}\t{{.Size}}\t{{.CreatedBy}}" "$IMAGE" | head -20
echo ""
echo "=== Image size ==="
docker image inspect "$IMAGE" --format '{{.Size}}' | numfmt --to=iec
echo ""
echo "=== Dangling images ==="
docker images -f dangling=true --format "{{.ID}} {{.Repository}}:{{.Tag}} {{.Size}}"
echo ""
echo "=== Recommendations ==="
echo "1. Check for OS packages left by apt-get install without --no-install-recommends"
echo "2. Check for copied files that are not needed at runtime (test data, docs)"
echo "3. Consider distroless base if no shell is needed"
echo "4. Chain RUN commands to reduce layers (apt-get update && apt-get install -y ... && rm -rf /var/lib/apt/lists/*)"
Phase 4: Kubernetes Deployment (2–3 hrs)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
namespace: production
labels:
app: app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: app
image: ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}
imagePullPolicy: Always
ports:
- containerPort: 3000
name: http
Phase 5: CI/CD Pipeline
name: Build & Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor
Phase 6: Full Orchestration
#!/usr/bin/env bash
set -euo pipefail
APP="${1:?Usage: $0 app-name env}"
ENV="${2:-staging}"
REGISTRY="${REGISTRY:-ghcr.io}/myorg"
echo "===> 1. Docker Compose — Verify local stack"
docker compose up -d --wait
docker compose down
echo "===> 2. Dockerfile — Build & optimize"
docker build --target=production -t "$REGISTRY/$APP:latest" .
docker tag "$REGISTRY/$APP:latest" "$REGISTRY/$APP:$(git rev-parse --short HEAD)"
echo "===> 3. Push"
docker push "$REGISTRY/$APP:latest"
docker push "$REGISTRY/$APP:$(git rev-parse --short HEAD)"
echo "===> 4. Deploy to K8s"
kubectl set image "deployment/$APP" "$APP=$REGISTRY/$APP:$(git rev-parse --short HEAD)" -n "$ENV"
kubectl rollout status "deployment/$APP" -n "$ENV" --=5m
kubectl get pods -n -l
First Action in 60 Minutes
- Add docker-compose.yml to any project — multi-service with health checks
- Write a multi-stage Dockerfile — builder stage + slim production stage
- Run the audit —
python dockerfile-optimize.py Dockerfile and fix top 3 issues
- Create
.dockerignore — copy the template above into the project
- Build & measure —
docker build -t test . && docker image ls | grep test — target <200 MB
- Scaffold K8s manifests —
deployment.yaml, service.yaml, hpa.yaml for the service
Anti-Rationalization Table
| Rationalization | Reality |
|---|
| "Production Dockerfile is fine" | 9/10 client Dockerfiles are single-stage with full OS images. Shrink from 1.2 GB to 150 MB = $50/mo savings per node. |
| "Compose is just for dev" | Compose with profiles powers CI, e2e tests, and staging environments. |
| "K8s is overkill for small teams" | A 3-node K8s cluster costs $30/mo and eliminates deployment anxiety. Clients pay $3,000+ for that peace of mind. |
| "Volumes are for data" | Named volumes for databases, bind mounts for code. Mixing them is the #1 Docker Compose bug in client projects. |
| "I will add health checks later" | Without health checks, K8s routes traffic to dead pods. Clients lose money. Add them first. |
Client Deliverable Checklist
Output Format
client-infra/
compose/
docker-compose.yml
.env.example
.dockerignore
docker/
Dockerfile
dockerfile-optimize.py
k8s/
base/
deployment.yaml
service.yaml
configmap.yaml
overlays/
staging/
kustomization.yaml
ingress.yaml
production/
kustomization.yaml
hpa.yaml
ingress.yaml
ci/
deploy.yml # GitHub Actions workflow
scripts/
deploy.sh
analyze-image.sh
Verification Checklist
When to Use
Use this skill when working with docker.
Workflow
See the parent skill for authoritative workflow documentation.