Claude
Skill
container-orchestration
Docker, Kubernetes, and AWS ECS/Fargate patterns. Triggers on: Dockerfile, docker-compose, kubernetes, k8s, helm, pod, deployment, service, ingress, container, image, ecs, fargate, task definition, ecs service, awsvpc, FARGATE_SPOT, ALB, ecs vs kubernetes.
Virus-scanned
Reviewed automatically before listing.
Download
0xdarkmatter-claude-mods-skills_container-orchestration-3dfaf0b.zip · 19 KB
Install
skills CLI
npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/container-orchestration
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
Git
git clone https://github.com/0xDarkMatter/claude-mods.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Container Orchestration
Facts verified as of 2026-07.
Docker and Kubernetes patterns for containerized applications.
Dockerfile Best Practices
# Use specific version, not :latest
FROM python:3.11-slim AS builder
# Set working directory
WORKDIR /app
# Copy dependency files first (better caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY src/ ./src/
# Production stage (multi-stage build)
FROM python:3.11-slim
WORKDIR /app
# Create non-root user
RUN useradd --create-home appuser
USER appuser
# Copy from builder
COPY --from=builder /app /app
# Set environment
ENV PYTHONUNBUFFERED=1
# Health check
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
EXPOSE 8000
CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0"]
Dockerfile Rules
DO:
- Use specific base image versions
- Use multi-stage builds
- Run as non-root user
- Order commands by change frequency
- Use .dockerignore
- Add health checks
DON'T:
- Use :latest tag
- Run as root
- Copy unnecessary files
- Store secrets in image
- Install dev dependencies in production
Docker Compose
# docker-compose.yml
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/app
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d app"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
Kubernetes Basics
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: app
image: myapp:1.0.0
ports:
- containerPort: 8000
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
Service
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 8000
type: ClusterIP
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-service
port:
number: 80
kubectl Quick Reference
| Command | Description |
|---|---|
kubectl get pods |
List pods |
kubectl logs <pod> |
View logs |
kubectl exec -it <pod> -- sh |
Shell into pod |
kubectl apply -f manifest.yaml |
Apply config |
kubectl rollout restart deployment/app |
Restart deployment |
kubectl rollout status deployment/app |
Check rollout |
kubectl describe pod <pod> |
Debug pod |
kubectl port-forward svc/app 8080:80 |
Local port forward |
Additional Resources
./references/dockerfile-patterns.md- Advanced Dockerfile techniques./references/k8s-manifests.md- Full Kubernetes manifest examples./references/helm-patterns.md- Helm chart structure and values./references/ecs-fargate.md- Amazon ECS on AWS Fargate (task definitions, services, awsvpc networking, IAM roles, secrets, scaling, ALB/NLB, ECS vs Kubernetes)
Scripts
./scripts/build-push.sh- Build and push Docker image
Assets
./assets/Dockerfile.template- Production Dockerfile template./assets/docker-compose.template.yml- Compose starter template
Files (claude-mods)
-
assets
-
docker-compose.template.yml 4.1 KB
# Docker Compose Template # For local development and testing version: "3.9" services: # ============================================================================== # Application # ============================================================================== app: build: context: . dockerfile: Dockerfile # For development, use debug target # target: debug image: ${IMAGE_NAME:-myapp}:${IMAGE_TAG:-latest} container_name: myapp restart: unless-stopped ports: - "${APP_PORT:-8000}:8000" environment: - DATABASE_URL=postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-myapp} - REDIS_URL=redis://redis:6379/0 - LOG_LEVEL=${LOG_LEVEL:-info} depends_on: db: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 10s volumes: # Development: mount source code # - ./src:/app/src:ro - app_logs:/app/logs networks: - app-network # ============================================================================== # Database # ============================================================================== db: image: postgres:15-alpine container_name: myapp-db restart: unless-stopped environment: POSTGRES_USER: ${DB_USER:-postgres} POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres} POSTGRES_DB: ${DB_NAME:-myapp} ports: - "${DB_PORT:-5432}:5432" volumes: - postgres_data:/var/lib/postgresql/data # - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-myapp}"] interval: 10s timeout: 5s retries: 5 networks: - app-network # ============================================================================== # Cache # ============================================================================== redis: image: redis:7-alpine container_name: myapp-redis restart: unless-stopped command: redis-server --appendonly yes ports: - "${REDIS_PORT:-6379}:6379" volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - app-network # ============================================================================== # Optional: Worker (for background jobs) # ============================================================================== # worker: # build: # context: . # dockerfile: Dockerfile # container_name: myapp-worker # restart: unless-stopped # command: python -m celery -A src.worker worker --loglevel=info # environment: # - DATABASE_URL=postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-myapp} # - REDIS_URL=redis://redis:6379/0 # depends_on: # - db # - redis # networks: # - app-network # ============================================================================== # Optional: Nginx (reverse proxy) # ============================================================================== # nginx: # image: nginx:alpine # container_name: myapp-nginx # restart: unless-stopped # ports: # - "80:80" # - "443:443" # volumes: # - ./nginx.conf:/etc/nginx/nginx.conf:ro # - ./certs:/etc/nginx/certs:ro # depends_on: # - app # networks: # - app-network # ============================================================================== # Volumes # ============================================================================== volumes: postgres_data: driver: local redis_data: driver: local app_logs: driver: local # ============================================================================== # Networks # ============================================================================== networks: app-network: driver: bridge -
Dockerfile.template 1.9 KB · in bundle
-
-
references
-
dockerfile-patterns.md 6.3 KB
# Advanced Dockerfile Patterns Production-ready Dockerfile techniques. ## Multi-Stage Builds ### Python Application ```dockerfile # Stage 1: Build dependencies FROM python:3.11-slim AS builder WORKDIR /app # Install build dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* # Create virtual environment RUN python -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" # Install dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Stage 2: Production image FROM python:3.11-slim WORKDIR /app # Copy virtual environment from builder COPY --from=builder /opt/venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" # Create non-root user RUN useradd --create-home --shell /bin/bash appuser USER appuser # Copy application COPY --chown=appuser:appuser src/ ./src/ EXPOSE 8000 CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0"] ``` ### Node.js Application ```dockerfile # Stage 1: Dependencies FROM node:20-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci --only=production # Stage 2: Build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Stage 3: Production FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=deps /app/node_modules ./node_modules COPY --from=builder /app/dist ./dist COPY --from=builder /app/package.json ./ USER nextjs EXPOSE 3000 CMD ["node", "dist/index.js"] ``` ### Go Application ```dockerfile # Stage 1: Build FROM golang:1.21-alpine AS builder WORKDIR /app # Cache dependencies COPY go.mod go.sum ./ RUN go mod download # Build COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server # Stage 2: Minimal runtime FROM scratch # Copy CA certificates for HTTPS COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # Copy binary COPY --from=builder /app/server /server EXPOSE 8080 ENTRYPOINT ["/server"] ``` ## Layer Optimization ### Order by Change Frequency ```dockerfile # Least frequently changed first FROM python:3.11-slim # System packages (rarely change) RUN apt-get update && apt-get install -y \ libpq5 \ && rm -rf /var/lib/apt/lists/* # Dependencies (change occasionally) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Application code (changes frequently) COPY src/ ./src/ CMD ["python", "-m", "src.main"] ``` ### Combine RUN Commands ```dockerfile # BAD - Multiple layers RUN apt-get update RUN apt-get install -y curl RUN apt-get install -y git RUN rm -rf /var/lib/apt/lists/* # GOOD - Single layer RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ git \ && rm -rf /var/lib/apt/lists/* ``` ## Security Best Practices ### Non-Root User ```dockerfile # Create user with specific UID RUN groupadd --gid 1000 appgroup \ && useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser # Switch to user USER appuser # Copy files with correct ownership COPY --chown=appuser:appgroup src/ ./src/ ``` ### Read-Only Root Filesystem ```dockerfile # Use with docker run --read-only FROM python:3.11-slim # Create writable directories RUN mkdir -p /tmp /var/log/app \ && chown -R appuser:appuser /tmp /var/log/app USER appuser # Application writes only to /tmp and /var/log/app ``` ### No Secrets in Image ```dockerfile # WRONG - Secret in build arg ARG API_KEY ENV API_KEY=${API_KEY} # CORRECT - Secret at runtime # Pass via environment variable or secret manager ENV API_KEY="" # Set at runtime ``` ### Minimal Base Image ```dockerfile # Full image: ~1GB FROM python:3.11 # Slim image: ~150MB FROM python:3.11-slim # Alpine image: ~50MB (but musl libc issues) FROM python:3.11-alpine # Distroless: Minimal, no shell FROM gcr.io/distroless/python3-debian12 ``` ## Health Checks ```dockerfile # HTTP health check HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # Without curl (for minimal images) HEALTHCHECK --interval=30s --timeout=3s \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" # TCP health check HEALTHCHECK --interval=30s --timeout=3s \ CMD nc -z localhost 8000 || exit 1 ``` ## Build Arguments ```dockerfile # Declare build args ARG PYTHON_VERSION=3.11 ARG APP_ENV=production FROM python:${PYTHON_VERSION}-slim # Use in ENV ARG APP_ENV ENV APP_ENV=${APP_ENV} # Conditional logic RUN if [ "$APP_ENV" = "development" ]; then \ pip install debugpy pytest; \ fi ``` ## Caching Strategies ### Mount Cache (BuildKit) ```dockerfile # syntax=docker/dockerfile:1.4 # Cache pip downloads RUN --mount=type=cache,target=/root/.cache/pip \ pip install -r requirements.txt # Cache apt packages RUN --mount=type=cache,target=/var/cache/apt \ apt-get update && apt-get install -y curl ``` ### Bind Mounts for Build ```dockerfile # syntax=docker/dockerfile:1.4 # Mount source code without copying RUN --mount=type=bind,source=src,target=/app/src \ python -m compileall /app/src ``` ## Labels and Metadata ```dockerfile LABEL org.opencontainers.image.title="My App" LABEL org.opencontainers.image.description="Production application" LABEL org.opencontainers.image.version="1.0.0" LABEL org.opencontainers.image.vendor="Company" LABEL org.opencontainers.image.source="https://github.com/org/repo" ``` ## .dockerignore ``` # .dockerignore .git .gitignore .env .env.* *.md !README.md Dockerfile* docker-compose* .dockerignore # Python __pycache__ *.pyc *.pyo .pytest_cache .coverage htmlcov .venv venv # Node node_modules npm-debug.log .npm # IDE .idea .vscode *.swp ``` ## Debug Container ```dockerfile # Multi-stage with debug target FROM python:3.11-slim AS base WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY src/ ./src/ # Debug stage FROM base AS debug RUN pip install debugpy CMD ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "src.main"] # Production stage FROM base AS production USER appuser CMD ["python", "-m", "src.main"] ``` Build specific target: ```bash docker build --target debug -t myapp:debug . docker build --target production -t myapp:latest . ``` -
ecs-fargate.md 6.7 KB
# Amazon ECS on AWS Fargate The rest of this skill covers Docker and Kubernetes. ECS on Fargate is a distinct orchestrator: AWS-native, serverless containers — no nodes to patch, no kubelet, no control plane to run. Reach here for production-grade Fargate workloads. Verify specifics against the canonical sources (links per section); ECS features move across platform versions. ## ECS vs Kubernetes — when to pick Fargate | | ECS on Fargate | Kubernetes (EKS / self-managed) | |---|---|---| | Ops burden | None — no nodes, no control plane | You run/patch nodes (EKS manages the control plane) | | Portability | AWS-only | Portable across clouds | | Ecosystem | AWS-native (ALB, IAM, CloudWatch, Secrets Manager) | Huge CNCF ecosystem, Helm, operators | | Learning curve | Low | High | | Best for | AWS-committed teams wanting minimal infra ops | Multi-cloud, complex scheduling, existing k8s investment | Pick Fargate when you're AWS-committed and want to stop managing container hosts. Pick Kubernetes when you need portability, advanced scheduling, or already run k8s. Note **EKS can also run on Fargate** — that's k8s pods on serverless capacity, a different product from the ECS-on-Fargate covered here. ## Core building blocks - **Task definition** — the blueprint: container image(s), CPU/memory at the task level (Fargate requires valid CPU/memory pairs, e.g. 0.25 vCPU / 0.5 GB up to 16 vCPU / 120 GB), `awsvpc` network mode (mandatory on Fargate), log config, the two IAM roles (below), and `secrets` mappings. - **Service** — keeps N task copies running, registers them with a load balancer, handles rolling or blue/green deploys, and integrates Service Auto Scaling and Service Connect / service discovery. - **Cluster** — logical grouping; with Fargate it's effectively just a namespace plus a capacity-provider strategy. - **Capacity providers** — `FARGATE` (on-demand) and `FARGATE_SPOT` (interruptible, up to ~70% cheaper). Mix them with a strategy (e.g. a base of FARGATE plus a weighted FARGATE_SPOT layer) to cut cost while protecting a reliable baseline. Architecting for AWS Fargate: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html · ECS Developer Guide: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html ## Networking (awsvpc) Every Fargate task gets its own ENI with a private IP — security groups attach to the *task*, not a shared host. - Run tasks in **private subnets**; reach the internet via a NAT Gateway, or skip NAT with **VPC interface endpoints** (ECR, Secrets Manager, CloudWatch Logs, etc.) to cut NAT cost and keep traffic on the AWS backbone. - **Security groups**: the task SG is the source. Common gotcha — a task can't reach RDS even though SGs "look right": the RDS SG must allow inbound *from the task's SG* (reference the SG id, not a CIDR), and the task must be in a subnet with a route to RDS. - **Load balancing**: **ALB** for HTTP/HTTPS (path/host routing, TLS termination) — the default for web services; **NLB** for TCP/UDP, ultra-low latency, or a static IP. The service registers task ENIs into the target group as `ip` targets. - **Service Connect** (preferred) or ECS Service Discovery for service-to-service communication with built-in naming and health. ## IAM — two distinct roles | Role | Used by | Grants | |------|---------|--------| | **Task execution role** | the ECS agent, at launch | Pull the image from ECR, write logs, fetch `secrets` from Secrets Manager / SSM | | **Task role** | your application, at runtime | The app's own AWS permissions (S3, DynamoDB, SQS…) | Keep them separate and least-privilege. Don't grant the app's S3 access on the execution role, and don't put image-pull permission on the task role. ## Secrets Inject via the task definition `secrets` block from **Secrets Manager** or **SSM Parameter Store** — values land as env vars at start, never baked into the image or committed. The *execution role* needs read access to the secret (and to the KMS key if the secret is CMK-encrypted). ## Scaling & cost - **Service Auto Scaling** via Application Auto Scaling: target tracking (e.g. hold CPU at 60%, or scale on ALB requests-per-target), step scaling, or scheduled scaling for known peaks. - **Cost levers**: FARGATE_SPOT for fault-tolerant/stateless work, right-size task CPU/memory (Container Insights shows actual utilization), Compute Savings Plans for steady baseline load, VPC endpoints to drop NAT data charges. ## Observability & deploys - **Logging** — `awslogs` driver to CloudWatch is the simple default; **FireLens** (Fluent Bit sidecar) when you need to route/transform logs to a third party or multiple sinks. - **Metrics** — CloudWatch Container Insights for per-task CPU/memory/network; alarm on those. - **Debugging** — **ECS Exec** opens an interactive shell into a running task (no SSH, no public IP). Needs ECS Exec enabled on the service and the SSM permissions on the task role. - **Deploys** — rolling (built-in, `minimumHealthyPercent` / `maximumPercent`) or **blue/green** via CodeDeploy (shift traffic, auto-rollback on alarm). Always wire container **health checks** so a bad revision fails the deployment instead of serving errors. ## Deployment tooling | Tool | Best for | |------|----------| | **AWS Copilot** | Fastest path — opinionated, generates the VPC/ALB/service for you. Great for getting a service live and managing environments. | | **AWS CDK** | Real IaC with `ecs-patterns` L3 constructs (e.g. `ApplicationLoadBalancedFargateService`); programmable, testable. | | **CloudFormation** | Declarative, no extra runtime; verbose. | | **Terraform** | When the org standardizes on Terraform across clouds. | Copilot to start fast; CDK/Terraform when you need full control and review. ## Common failure modes | Symptom | Likely cause | |---------|--------------| | Task can't reach RDS (timeout) | RDS SG doesn't allow the task SG; or task subnet has no route | | `CannotPullContainerError` | Execution role lacks ECR pull, or no route to ECR (no NAT / no VPC endpoint) | | Secret injection fails at start | Execution role missing Secrets Manager / SSM (and KMS) read permission | | Tasks killed under load | Task CPU/memory under-provisioned — check Container Insights, right-size | | Spot tasks vanish | FARGATE_SPOT interruption — add a FARGATE base in the capacity strategy | ## Canonical references - AWS Fargate architecture: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html - ECS Developer Guide: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html - ECS Best Practices: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-best-practices.html - Getting started with Fargate: https://aws.amazon.com/fargate/getting-started/ -
helm-patterns.md 7.4 KB
# Helm Chart Patterns Production Helm chart structure and patterns. ## Chart Structure ``` myapp/ ├── Chart.yaml ├── values.yaml ├── values-staging.yaml ├── values-production.yaml ├── templates/ │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── hpa.yaml │ ├── pdb.yaml │ └── NOTES.txt └── charts/ # Dependencies ``` ## Chart.yaml ```yaml apiVersion: v2 name: myapp description: My Application Helm Chart type: application version: 1.0.0 appVersion: "2.0.0" keywords: - web - api maintainers: - name: Team email: team@example.com dependencies: - name: postgresql version: "12.x.x" repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled ``` ## values.yaml ```yaml # Default values for myapp replicaCount: 3 image: repository: myregistry/myapp pullPolicy: IfNotPresent tag: "" # Defaults to appVersion imagePullSecrets: [] nameOverride: "" fullnameOverride: "" serviceAccount: create: true annotations: {} name: "" podAnnotations: {} podSecurityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL service: type: ClusterIP port: 80 ingress: enabled: false className: nginx annotations: {} hosts: - host: app.example.com paths: - path: / pathType: Prefix tls: [] resources: limits: cpu: 500m memory: 512Mi requests: cpu: 100m memory: 128Mi autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 80 pdb: enabled: true minAvailable: 2 nodeSelector: {} tolerations: [] affinity: {} # Application config config: logLevel: info cacheTtl: 3600 # Secrets (use external secrets in production) secrets: databaseUrl: "" apiKey: "" # Database dependency postgresql: enabled: false auth: database: myapp ``` ## Helper Template (_helpers.tpl) ```yaml {{/* Expand the name of the chart. */}} {{- define "myapp.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create a default fully qualified app name. */}} {{- define "myapp.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* Create chart name and version as used by the chart label. */}} {{- define "myapp.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} {{- define "myapp.labels" -}} helm.sh/chart: {{ include "myapp.chart" . }} {{ include "myapp.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "myapp.selectorLabels" -}} app.kubernetes.io/name: {{ include "myapp.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} {{- define "myapp.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "myapp.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} ``` ## Deployment Template ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "myapp.fullname" . }} labels: {{- include "myapp.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "myapp.selectorLabels" . | nindent 6 }} template: metadata: annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "myapp.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "myapp.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 8000 protocol: TCP livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 10 resources: {{- toYaml .Values.resources | nindent 12 }} envFrom: - configMapRef: name: {{ include "myapp.fullname" . }} - secretRef: name: {{ include "myapp.fullname" . }} volumeMounts: - name: tmp mountPath: /tmp volumes: - name: tmp emptyDir: {} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} ``` ## Helm Commands ```bash # Install helm install myapp ./myapp -f values-production.yaml # Upgrade helm upgrade myapp ./myapp -f values-production.yaml # Dry run helm install myapp ./myapp --dry-run --debug # Template output helm template myapp ./myapp -f values-production.yaml # Rollback helm rollback myapp 1 # History helm history myapp # Uninstall helm uninstall myapp ``` ## Environment-Specific Values ### values-staging.yaml ```yaml replicaCount: 2 ingress: enabled: true hosts: - host: staging.app.example.com paths: - path: / pathType: Prefix tls: - secretName: staging-tls hosts: - staging.app.example.com resources: limits: cpu: 250m memory: 256Mi requests: cpu: 50m memory: 64Mi autoscaling: enabled: false ``` ### values-production.yaml ```yaml replicaCount: 3 ingress: enabled: true annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" hosts: - host: app.example.com paths: - path: / pathType: Prefix tls: - secretName: production-tls hosts: - app.example.com resources: limits: cpu: 500m memory: 512Mi requests: cpu: 100m memory: 128Mi autoscaling: enabled: true minReplicas: 3 maxReplicas: 20 pdb: enabled: true minAvailable: 2 ``` -
k8s-manifests.md 6.9 KB
# Kubernetes Manifests Production Kubernetes configuration examples. ## Complete Application Stack ### Namespace ```yaml apiVersion: v1 kind: Namespace metadata: name: myapp labels: app: myapp ``` ### ConfigMap ```yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config namespace: myapp data: LOG_LEVEL: "info" CACHE_TTL: "3600" config.yaml: | server: port: 8000 workers: 4 database: pool_size: 10 ``` ### Secret ```yaml apiVersion: v1 kind: Secret metadata: name: app-secrets namespace: myapp type: Opaque stringData: DATABASE_URL: postgres://user:pass@db:5432/app API_KEY: supersecretkey --- # External Secrets (for AWS Secrets Manager, etc.) apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-secrets namespace: myapp spec: refreshInterval: 1h secretStoreRef: name: aws-secrets-manager kind: SecretStore target: name: app-secrets data: - secretKey: DATABASE_URL remoteRef: key: myapp/database-url ``` ### Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: app namespace: myapp labels: app: myapp version: v1 spec: replicas: 3 selector: matchLabels: app: myapp strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: myapp version: v1 annotations: prometheus.io/scrape: "true" prometheus.io/port: "8000" spec: serviceAccountName: app-service-account securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 containers: - name: app image: myregistry/myapp:1.0.0 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8000 protocol: TCP env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: DATABASE_URL resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 10 periodSeconds: 30 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL volumeMounts: - name: tmp mountPath: /tmp - name: config mountPath: /app/config readOnly: true volumes: - name: tmp emptyDir: {} - name: config configMap: name: app-config affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: myapp topologyKey: kubernetes.io/hostname ``` ### Service ```yaml apiVersion: v1 kind: Service metadata: name: app-service namespace: myapp spec: type: ClusterIP selector: app: myapp ports: - name: http port: 80 targetPort: http protocol: TCP ``` ### Ingress ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress namespace: myapp annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/proxy-body-size: "10m" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: ingressClassName: nginx tls: - hosts: - app.example.com secretName: app-tls rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-service port: number: 80 ``` ### HorizontalPodAutoscaler ```yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: app-hpa namespace: myapp spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: app minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 behavior: scaleDown: stabilizationWindowSeconds: 300 policies: - type: Percent value: 10 periodSeconds: 60 scaleUp: stabilizationWindowSeconds: 0 policies: - type: Percent value: 100 periodSeconds: 15 ``` ### PodDisruptionBudget ```yaml apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: app-pdb namespace: myapp spec: minAvailable: 2 selector: matchLabels: app: myapp ``` ### ServiceAccount and RBAC ```yaml apiVersion: v1 kind: ServiceAccount metadata: name: app-service-account namespace: myapp --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: app-role namespace: myapp rules: - apiGroups: [""] resources: ["configmaps", "secrets"] verbs: ["get", "list"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: app-role-binding namespace: myapp subjects: - kind: ServiceAccount name: app-service-account namespace: myapp roleRef: kind: Role name: app-role apiGroup: rbac.authorization.k8s.io ``` ### NetworkPolicy ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: app-network-policy namespace: myapp spec: podSelector: matchLabels: app: myapp policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: name: ingress-nginx ports: - protocol: TCP port: 8000 egress: - to: - namespaceSelector: matchLabels: name: database ports: - protocol: TCP port: 5432 - to: - namespaceSelector: {} ports: - protocol: UDP port: 53 # DNS ``` ### CronJob ```yaml apiVersion: batch/v1 kind: CronJob metadata: name: cleanup-job namespace: myapp spec: schedule: "0 2 * * *" # 2 AM daily concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: cleanup image: myregistry/myapp:1.0.0 command: ["python", "-m", "src.jobs.cleanup"] resources: limits: memory: "256Mi" cpu: "200m" ```
-
-
scripts
-
build-push.sh 5.3 KB
#!/usr/bin/env bash # Build and (optionally) push a Docker image with a predictable, agent-safe CLI. # # Usage: build-push.sh [--tag TAG] [--registry REG] [--push] # [--dockerfile FILE] [--context DIR] [--dry-run] [--help] # Input: argv only. Env overrides: IMAGE_NAME, IMAGE_TAG, DOCKER_REGISTRY. # Output: stdout carries the resolved plan as plain "Key: Value" data lines # (Image / Dockerfile / Context, [+ Pushed] after a push) — identical # data to the pre-backfill behaviour, so downstream parsers are # unaffected. Under --dry-run the planned `docker build` command is # appended and nothing is executed. # Stderr: progress banners ("=== Building ... ===") and diagnostics. # Exit: 0 ok, 2 usage (unknown flag or missing value), 5 missing-dep (docker # not on PATH), 1 build or push failed. # # Examples: # build-push.sh --tag v1.2.3 --registry ghcr.io/acme --push # IMAGE_NAME=svc build-push.sh --dry-run --tag dev # build-push.sh --dockerfile Dockerfile.prod --context ./app set -uo pipefail usage() { cat <<'EOF' Usage: build-push.sh [OPTIONS] Build and optionally push a Docker image. Options: -t, --tag TAG Image tag (default: $IMAGE_TAG or "latest"). -r, --registry REG Registry prefix, e.g. ghcr.io/acme (default: $DOCKER_REGISTRY). -p, --push Push the image after a successful build. -f, --dockerfile FILE Dockerfile path (default: Dockerfile). -c, --context DIR Build context directory (default: .). -n, --dry-run Resolve and print the plan WITHOUT invoking docker. -h, --help Show this help and exit. Environment: IMAGE_NAME Image name (default: current directory basename). IMAGE_TAG Default tag. DOCKER_REGISTRY Default registry prefix. Exit codes: 0 success 2 usage error (unknown flag or missing value) 5 docker is not installed (missing dependency) 1 build or push failed Examples: build-push.sh --tag v1.2.3 --registry ghcr.io/acme --push IMAGE_NAME=svc build-push.sh --dry-run --tag dev build-push.sh --dockerfile Dockerfile.prod --context ./app EOF } # Defaults REGISTRY="${DOCKER_REGISTRY:-}" TAG="${IMAGE_TAG:-latest}" PUSH=false DOCKERFILE="Dockerfile" CONTEXT="." DRY_RUN=false # Parse arguments — runs with NO docker dependency so --help / validation never # require a running daemon. need_value() { echo "build-push.sh: $1 requires a value" >&2 exit 2 } while [[ $# -gt 0 ]]; do case $1 in --tag|-t) [[ $# -ge 2 ]] || need_value "$1"; TAG="$2"; shift 2 ;; --registry|-r) [[ $# -ge 2 ]] || need_value "$1"; REGISTRY="$2"; shift 2 ;; --push|-p) PUSH=true; shift ;; --dockerfile|-f) [[ $# -ge 2 ]] || need_value "$1"; DOCKERFILE="$2"; shift 2 ;; --context|-c) [[ $# -ge 2 ]] || need_value "$1"; CONTEXT="$2"; shift 2 ;; --dry-run|-n) DRY_RUN=true; shift ;; --help|-h) usage; exit 0 ;; *) echo "build-push.sh: unknown option: $1" >&2 echo "Run 'build-push.sh --help' for usage." >&2 exit 2 ;; esac done # Resolve image name (env override, else current directory) and full reference. IMAGE_NAME="${IMAGE_NAME:-$(basename "$(pwd)")}" if [[ -n "$REGISTRY" ]]; then FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}" else FULL_IMAGE="${IMAGE_NAME}:${TAG}" fi # Progress banners are chatter -> stderr; the resolved Image/Dockerfile/Context # are the data product -> stdout (unchanged from pre-backfill so anything that # keyed off "Image: ..." keeps working). banner() { printf '=== %s ===\n' "$*" >&2; } # Dry-run: validate args and print the plan, never touching docker. if [[ "$DRY_RUN" = true ]]; then banner "Dry run (no docker invoked)" printf 'Image: %s\n' "$FULL_IMAGE" printf 'Dockerfile: %s\n' "$DOCKERFILE" printf 'Context: %s\n' "$CONTEXT" printf 'Push: %s\n' "$PUSH" printf 'Plan: docker build -t %s -f %s %s' "$FULL_IMAGE" "$DOCKERFILE" "$CONTEXT" [[ "$PUSH" = true ]] && printf ' && docker push %s' "$FULL_IMAGE" printf '\n' exit 0 fi # Real path: docker is required from here on. if ! command -v docker >/dev/null 2>&1; then echo "build-push.sh: docker is not installed (or not on PATH)." >&2 echo " Install Docker: https://docs.docker.com/get-docker/" >&2 exit 5 fi banner "Building Docker Image" printf 'Image: %s\n' "$FULL_IMAGE" printf 'Dockerfile: %s\n' "$DOCKERFILE" printf 'Context: %s\n' "$CONTEXT" printf '\n' # Build — a failed build is a runtime error (exit 1), distinct from usage (2). if ! docker build \ -t "$FULL_IMAGE" \ -f "$DOCKERFILE" \ --build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ --build-arg VCS_REF="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" \ "$CONTEXT"; then echo "build-push.sh: docker build failed" >&2 exit 1 fi banner "Build Complete" printf 'Image: %s\n' "$FULL_IMAGE" # Push if requested. if [[ "$PUSH" = true ]]; then banner "Pushing Image" if ! docker push "$FULL_IMAGE"; then echo "build-push.sh: docker push failed" >&2 exit 1 fi printf 'Pushed: %s\n' "$FULL_IMAGE" fi # Show image info. banner "Image Info" docker images "$FULL_IMAGE" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
-
-
tests
-
run.sh 7.7 KB
#!/usr/bin/env bash # Self-test for container-orchestration — fully offline: never runs a real # `docker build`/`docker push` and never calls a real registry. # # build-push.sh ships real side effects, so this suite exercises ONLY its safe # surfaces: the protocol contract (--help / EXAMPLES / exit codes), arg parsing # through the no-docker `--dry-run` path, stream separation (data on stdout, # chatter on stderr), and a pre/post proof that the happy-path DATA output is # unchanged by the protocol backfill (only the chatter banners and --help were # added/moved). A sentinel `docker` on PATH proves the help/parse/dry-run paths # never invoke docker at all. # # Usage: bash tests/run.sh # Exit: 0 all pass, 1 one or more failures set -uo pipefail HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SKILL="$(dirname "$HERE")" V="$SKILL/scripts/build-push.sh" # Pre-backfill git blob of build-push.sh — used for a live pre/post data diff # when the object is still reachable in the repo's history. OLD_BLOB="0d6b10cdef2901784b3360a2c61ba2ea144b6bc2" SB="$(mktemp -d)"; trap 'rm -rf "$SB"' EXIT PASS=0; FAIL=0 ok() { PASS=$((PASS+1)); printf ' PASS %s\n' "$1"; } no() { FAIL=$((FAIL+1)); printf ' FAIL %s\n' "$1"; } expect_exit() { [[ "$2" == "$3" ]] && ok "$1 (exit $3)" || no "$1 (want $2 got $3)"; } expect_has() { case "$3" in *"$2"*) ok "$1";; *) no "$1 (missing '$2')";; esac; } # Sentinel `docker`: records any invocation into $DOCKER_SENTINEL_LOG, then # exits nonzero WITHOUT performing a real build/push. Prepended to PATH so we # can prove the safe paths never touch docker (and the baseline run of the old # script likewise performs no real build). mkdir -p "$SB/bin" cat > "$SB/bin/docker" <<'EOF' #!/usr/bin/env bash # Test sentinel — never builds/pushes; just records the call. printf 'docker %s\n' "$*" >> "${DOCKER_SENTINEL_LOG:-/dev/null}" exit 1 EOF chmod +x "$SB/bin/docker" NO_DOCKER_PATH="$SB/bin:$PATH" no_docker_calls() { [[ ! -s "$SB/docker.log" ]]; } # true == nothing was logged echo "=== container-orchestration self-test ===" # ── syntax + contract header ───────────────────────────────────────────────── echo "-- contract --" bash -n "$V" 2>/dev/null && ok "bash -n build-push.sh" || no "bash -n build-push.sh" hdr="$(head -25 "$V")" expect_has "header has Usage" "Usage:" "$hdr" expect_has "header has Output" "Output:" "$hdr" expect_has "header has Stderr" "Stderr:" "$hdr" expect_has "header has Exit" "Exit:" "$hdr" expect_has "header has Examples" "xamples" "$hdr" # ── --help / -h ────────────────────────────────────────────────────────────── echo "-- help --" PATH="$NO_DOCKER_PATH" bash "$V" --help >/dev/null 2>&1; expect_exit "--help exits 0" 0 $? PATH="$NO_DOCKER_PATH" bash "$V" -h >/dev/null 2>&1; expect_exit "-h exits 0" 0 $? out="$(PATH="$NO_DOCKER_PATH" bash "$V" --help 2>/dev/null)" expect_has "--help lists --tag" "--tag" "$out" expect_has "--help lists --registry" "--registry" "$out" expect_has "--help lists --push" "--push" "$out" expect_has "--help lists --dry-run" "--dry-run" "$out" expect_has "--help documents exit 2" "2" "$out" expect_has "--help documents exit 5" "5" "$out" rm -f "$SB/docker.log" PATH="$NO_DOCKER_PATH" DOCKER_SENTINEL_LOG="$SB/docker.log" bash "$V" --help >/dev/null 2>&1 no_docker_calls && ok "--help invokes no docker" || no "--help invoked docker" # ── usage errors (exit 2) ──────────────────────────────────────────────────── echo "-- usage errors --" PATH="$NO_DOCKER_PATH" bash "$V" --bogus >/dev/null 2>&1; expect_exit "unknown flag -> 2" 2 $? PATH="$NO_DOCKER_PATH" bash "$V" --tag >/dev/null 2>&1; expect_exit "missing --tag value -> 2" 2 $? PATH="$NO_DOCKER_PATH" bash "$V" --registry >/dev/null 2>&1; expect_exit "missing --registry value -> 2" 2 $? rm -f "$SB/docker.log" PATH="$NO_DOCKER_PATH" DOCKER_SENTINEL_LOG="$SB/docker.log" bash "$V" --bogus >/dev/null 2>&1 no_docker_calls && ok "arg-parse error invokes no docker" || no "arg-parse error invoked docker" # ── arg parsing + resolution via --dry-run (no docker) ─────────────────────── echo "-- dry-run resolution --" rm -f "$SB/docker.log" out="$(PATH="$NO_DOCKER_PATH" DOCKER_SENTINEL_LOG="$SB/docker.log" \ IMAGE_NAME=myapp bash "$V" --dry-run --tag v1 --registry ghcr.io/acme 2>/dev/null)"; rc=$? expect_exit "dry-run exits 0" 0 "$rc" expect_has "resolves full image" "Image: ghcr.io/acme/myapp:v1" "$out" expect_has "default dockerfile" "Dockerfile: Dockerfile" "$out" expect_has "default context" "Context: ." "$out" expect_has "push flag shown false" "Push: false" "$out" no_docker_calls && ok "dry-run invokes no docker" || no "dry-run invoked docker" out="$(PATH="$NO_DOCKER_PATH" IMAGE_NAME=svc bash "$V" --dry-run --push --tag dev 2>/dev/null)" expect_has "no registry -> bare image" "Image: svc:dev" "$out" expect_has "push flag shown true" "Push: true" "$out" expect_has "plan includes build" "docker build -t svc:dev -f Dockerfile ." "$out" expect_has "plan includes push" "docker push svc:dev" "$out" out="$(PATH="$NO_DOCKER_PATH" IMAGE_NAME=app bash "$V" --dry-run \ --dockerfile Dockerfile.prod --context ./builds/app 2>/dev/null)" expect_has "--dockerfile honoured" "Dockerfile: Dockerfile.prod" "$out" expect_has "--context honoured" "Context: ./builds/app" "$out" # ── stream separation: banners on stderr, data on stdout ───────────────────── echo "-- stream separation --" err="$(PATH="$NO_DOCKER_PATH" IMAGE_NAME=x bash "$V" --dry-run 2>&1 1>/dev/null)" expect_has "banner on stderr" "===" "$err" dat="$(PATH="$NO_DOCKER_PATH" IMAGE_NAME=x bash "$V" --dry-run 2>/dev/null)" case "$dat" in *"==="*) no "stdout leaked a banner";; *) ok "stdout carries only data lines";; esac # ── happy-path DATA preserved pre/post (only chatter/help moved) ───────────── echo "-- data preservation (pre/post) --" # Captured baseline = the pre-backfill happy-path stdout data lines. These must # stay byte-identical so downstream parsers are unaffected. BASELINE=$'Image: ghcr.io/acme/myapp:v1\nDockerfile: Dockerfile\nContext: .' new_data="$(PATH="$NO_DOCKER_PATH" IMAGE_NAME=myapp bash "$V" --dry-run \ --tag v1 --registry ghcr.io/acme 2>/dev/null \ | grep -E '^(Image|Dockerfile|Context): ')" [[ "$new_data" == "$BASELINE" ]] \ && ok "data lines match captured baseline" \ || { no "data lines match captured baseline"; printf ' got:\n%s\n' "$new_data" >&2; } # Live pre/post diff against the actual pre-backfill blob, when reachable. if OLD_SRC="$(git -C "$SKILL/../../.." cat-file -p "$OLD_BLOB" 2>/dev/null)"; then printf '%s' "$OLD_SRC" > "$SB/old-build-push.sh" old_data="$(cd "$SB" && PATH="$NO_DOCKER_PATH" DOCKER_SENTINEL_LOG="$SB/old-docker.log" \ IMAGE_NAME=myapp bash "$SB/old-build-push.sh" --tag v1 --registry ghcr.io/acme 2>/dev/null \ | grep -E '^(Image|Dockerfile|Context): ')" if [[ -n "$old_data" && "$old_data" == "$new_data" ]]; then ok "pre/post data identical (live diff vs old blob)" else no "pre/post data identical (live diff vs old blob)" fi else echo " SKIP live pre/post diff (old blob $OLD_BLOB not reachable)" fi echo "" echo "=== $PASS passed, $FAIL failed ===" [[ "$FAIL" -eq 0 ]] || exit 1 exit 0
-
-
SKILL.md 5.3 KB
--- name: container-orchestration description: "Docker, Kubernetes, and AWS ECS/Fargate patterns. Triggers on: Dockerfile, docker-compose, kubernetes, k8s, helm, pod, deployment, service, ingress, container, image, ecs, fargate, task definition, ecs service, awsvpc, FARGATE_SPOT, ALB, ecs vs kubernetes." when_to_use: "Use when running containers at scale — Dockerfile/compose authoring plus Kubernetes or ECS/Fargate orchestration — e.g. 'write a multi-stage Dockerfile', 'author a Helm chart or k8s Deployment+Service', 'set up an ECS Fargate task definition', 'ECS vs Kubernetes'. Reach for docker-ops for Docker-only build/compose work." license: MIT compatibility: "Docker 20+, Kubernetes 1.25+, Helm 3+" allowed-tools: "Read Write Bash" metadata: author: claude-mods --- # Container Orchestration > Facts verified as of 2026-07. Docker and Kubernetes patterns for containerized applications. ## Dockerfile Best Practices ```dockerfile # Use specific version, not :latest FROM python:3.11-slim AS builder # Set working directory WORKDIR /app # Copy dependency files first (better caching) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY src/ ./src/ # Production stage (multi-stage build) FROM python:3.11-slim WORKDIR /app # Create non-root user RUN useradd --create-home appuser USER appuser # Copy from builder COPY --from=builder /app /app # Set environment ENV PYTHONUNBUFFERED=1 # Health check HEALTHCHECK --interval=30s --timeout=3s \ CMD curl -f http://localhost:8000/health || exit 1 EXPOSE 8000 CMD ["python", "-m", "uvicorn", "src.main:app", "--host", "0.0.0.0"] ``` ### Dockerfile Rules ``` DO: - Use specific base image versions - Use multi-stage builds - Run as non-root user - Order commands by change frequency - Use .dockerignore - Add health checks DON'T: - Use :latest tag - Run as root - Copy unnecessary files - Store secrets in image - Install dev dependencies in production ``` ## Docker Compose ```yaml # docker-compose.yml version: "3.9" services: app: build: context: . dockerfile: Dockerfile ports: - "8000:8000" environment: - DATABASE_URL=postgres://user:pass@db:5432/app depends_on: db: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 db: image: postgres:15-alpine volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: app healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d app"] interval: 10s timeout: 5s retries: 5 volumes: postgres_data: ``` ## Kubernetes Basics ### Deployment ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: app labels: app: myapp spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: labels: app: myapp spec: containers: - name: app image: myapp:1.0.0 ports: - containerPort: 8000 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /ready port: 8000 initialDelaySeconds: 5 periodSeconds: 10 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: database-url ``` ### Service ```yaml apiVersion: v1 kind: Service metadata: name: app-service spec: selector: app: myapp ports: - port: 80 targetPort: 8000 type: ClusterIP ``` ### Ingress ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: app-service port: number: 80 ``` ## kubectl Quick Reference | Command | Description | |---------|-------------| | `kubectl get pods` | List pods | | `kubectl logs <pod>` | View logs | | `kubectl exec -it <pod> -- sh` | Shell into pod | | `kubectl apply -f manifest.yaml` | Apply config | | `kubectl rollout restart deployment/app` | Restart deployment | | `kubectl rollout status deployment/app` | Check rollout | | `kubectl describe pod <pod>` | Debug pod | | `kubectl port-forward svc/app 8080:80` | Local port forward | ## Additional Resources - `./references/dockerfile-patterns.md` - Advanced Dockerfile techniques - `./references/k8s-manifests.md` - Full Kubernetes manifest examples - `./references/helm-patterns.md` - Helm chart structure and values - `./references/ecs-fargate.md` - Amazon ECS on AWS Fargate (task definitions, services, awsvpc networking, IAM roles, secrets, scaling, ALB/NLB, ECS vs Kubernetes) ## Scripts - `./scripts/build-push.sh` - Build and push Docker image ## Assets - `./assets/Dockerfile.template` - Production Dockerfile template - `./assets/docker-compose.template.yml` - Compose starter template
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.