Apply the AI FIRST principle: never accept first-pass quality. Minimum 2 iterations. Read all output, improve every section. No shortcuts.
📋 Overview
This skill provides comprehensive patterns for containerizing GitHub Agentic Workflows using Docker and container orchestration platforms. It covers container isolation strategies, security hardening techniques, multi-stage build optimization, Docker Compose orchestration, and Kubernetes deployment patterns for production-ready autonomous agent systems.
🎯 Core Concepts
Container Architecture
graph TB
subgraph "Build Stage"
A[Source Code] --> B[Multi-Stage Build]
B --> C[Dependencies]
B --> D[Agent Code]
C --> E[Runtime Image]
D --> E
end
subgraph "Runtime Environment"
E --> F[Container Runtime]
F --> G[Agent Process]
G --> H[MCP Servers]
G --> I[LLM APIs]
G --> J[GitHub API]
end
subgraph "Orchestration"
F --> K[Docker Compose]
F --> L[Kubernetes]
K --> M[Multi-Container]
L --> N[Scalable Deployment]
end
subgraph "Security"
F --> O[Non-Root User]
F --> P[Read-Only FS]
F --> Q[Resource Limits]
end
style E fill:#00d9ff
style G fill:#ff006e
style O fill:#ffbe0b
Key Principles
Isolation: Containers provide process and resource isolation
Immutability: Containers are built once, run anywhere
Security: Defense-in-depth with multiple security layers
Efficiency: Minimal image size and resource usage
Orchestration: Automated deployment and scaling
Observability: Comprehensive logging and monitoring
🏗️ Multi-Stage Builds
1. Optimized Node.js Agent
# docker/agent-node.Dockerfile
# Multi-stage build for Node.js agentic workflow
# Stage 1: Build dependencies
FROM node:24-alpine AS deps
LABEL stage=deps
WORKDIR /app
# Copy dependency manifests
COPY package.json package-lock.json ./
# Install dependencies (production only)
RUN npm ci --only=production \
&& npm cache clean --force
# Stage 2: Build application
FROM node:24-alpine AS builder
LABEL stage=builder
WORKDIR /app
# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy source code
COPY scripts/agents ./scripts/agents
COPY .github/copilot-mcp.json ./config/
# Build TypeScript if needed
# RUN npm run build
# Stage 3: Production runtime
FROM node:24-alpine AS runtime
# Install security updates
RUN apk update && apk upgrade \
&& apk add --no-cache \
dumb-init \
ca-certificates \
&& rm -rf /var/cache/apk/*
# Create non-root user
RUN addgroup -g 1001 -S agent \
&& adduser -u 1001 -S agent -G agent
WORKDIR /app
# Copy production dependencies
COPY --from=deps --chown=agent:agent /app/node_modules ./node_modules
# Copy application code
COPY --from=builder --chown=agent:agent /app/scripts/agents ./scripts/agents
COPY --from=builder --chown=agent:agent /app/config ./config
# Set environment variables
ENV NODE_ENV=production \
NODE_OPTIONS="--max-old-space-size=2048" \
LOG_LEVEL=info
# Switch to non-root user
USER agent
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1); });"
# Expose port (if needed)
EXPOSE 3000
# Use dumb-init to handle signals properly
ENTRYPOINT ["dumb-init", "--"]
# Default command
CMD ["node", "scripts/agents/main.js"]
# docker/agent-multi.Dockerfile
# Multi-stage build with Node.js and Python
# Stage 1: Node.js dependencies
FROM node:24-alpine AS node-deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && npm cache clean --force
# Stage 2: Python dependencies
FROM python:3.11-slim AS python-deps
WORKDIR /app
COPY requirements.txt ./
RUN python -m venv /opt/venv \
&& /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# Stage 3: Production runtime
FROM ubuntu:22.04
# Install Node.js, Python, and runtime dependencies
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
dumb-init \
nodejs \
npm \
python3.11 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -m -u 1001 -s /bin/bash agent
WORKDIR /app
# Copy Node.js dependencies
COPY --from=node-deps --chown=agent:agent /app/node_modules ./node_modules
# Copy Python virtual environment
COPY --from=python-deps --chown=agent:agent /opt/venv /opt/venv
# Copy application code
COPY --chown=agent:agent scripts/agents ./scripts/agents
COPY --chown=agent:agent .github/copilot-mcp.json ./config/
# Set environment variables
ENV PATH="/opt/venv/bin:$PATH" \
NODE_ENV=production \
PYTHONUNBUFFERED=1
USER agent
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "scripts/agents/orchestrator.js"]
🔒 Security Hardening
1. Non-Root User
# Never run containers as root
# Create user and group
RUN addgroup -g 1001 -S agent \
&& adduser -u 1001 -S agent -G agent
# Set ownership
COPY --chown=agent:agent ./app ./app
# Switch to non-root user
USER agent
2. Read-Only Filesystem
# Make root filesystem read-only for security
# In Dockerfile: specify writable volumes
VOLUME ["/tmp", "/app/logs"]
# In docker-compose.yml
services:
agent:
read_only: true
tmpfs:
- /tmp:mode=1777
- /app/logs:mode=0755
# In KubernetesapiVersion:v1kind:Podspec:containers:-name:agentsecurityContext:readOnlyRootFilesystem:truevolumeMounts:-name:tmpmountPath:/tmp-name:logsmountPath:/app/logsvolumes:-name:tmpemptyDir: {}
-name:logsemptyDir: {}
3. Resource Limits
# Set resource limits in Dockerfile (documentation)
LABEL resource.memory="512Mi"
LABEL resource.cpu="0.5"
# Use BuildKit secrets for sensitive build-time data
# syntax=docker/dockerfile:1
FROM node:24-alpine
# Mount secret during build (never stored in image)
RUN --mount=type=secret,id=npm_token \
echo "//registry.npmjs.org/:_authToken=$(cat /run/secrets/npm_token)" > ~/.npmrc \
&& npm ci \
&& rm ~/.npmrc
# docker-compose.ymlversion:'3.8'services:# MCP Gatewaymcp-gateway:build:context:.dockerfile:docker/mcp-gateway.Dockerfilecontainer_name:mcp-gatewayrestart:unless-stoppedports:-"3000:3000"environment:-NODE_ENV=production-LOG_LEVEL=infovolumes:-./config/copilot-mcp.json:/app/config/mcp.json:ronetworks:-agent-networkhealthcheck:test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval:30stimeout:10sretries:3start_period:10s# Agent Runneragent:build:context:.dockerfile:docker/agent.Dockerfilecontainer_name:agent-runnerrestart:unless-stoppeddepends_on:mcp-gateway:condition:service_healthyredis:condition:service_healthyenvironment:-NODE_ENV=production-MCP_GATEWAY_URL=http://mcp-gateway:3000-REDIS_URL=redis://redis:6379-GITHUB_TOKEN=${GITHUB_TOKEN}-ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}volumes:-./logs:/app/logs-agent-data:/app/datanetworks:-agent-networkdeploy:resources:limits:cpus:'1'memory:1Greservations:cpus:'0.5'memory:512Msecurity_opt:-no-new-privileges:trueread_only:truetmpfs:-/tmp:mode=1777# Redis for caching and queuingredis:image:redis:7-alpinecontainer_name:redisrestart:unless-stoppedports:-"6379:6379"volumes:-redis-data:/datanetworks:-agent-networkcommand:redis-server--appendonlyyeshealthcheck:test: ["CMD", "redis-cli", "ping"]
interval:10stimeout:5sretries:5# PostgreSQL for persistent datapostgres:image:postgres:16-alpinecontainer_name:postgresrestart:unless-stoppedenvironment:-POSTGRES_DB=agentic_workflow-POSTGRES_USER=agent-POSTGRES_PASSWORD=${POSTGRES_PASSWORD}volumes:-postgres-data:/var/lib/postgresql/data-./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql:ronetworks:-agent-networkhealthcheck:test: ["CMD-SHELL", "pg_isready -U agent"]
interval:10stimeout:5sretries:5# Prometheus for metricsprometheus:image:prom/prometheus:latestcontainer_name:prometheusrestart:unless-stoppedports:-"9090:9090"volumes:-./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro-prometheus-data:/prometheusnetworks:-agent-networkcommand:-'--config.file=/etc/prometheus/prometheus.yml'-'--storage.tsdb.path=/prometheus'-'--storage.tsdb.retention.time=15d'# Grafana for visualizationgrafana:image:grafana/grafana:latestcontainer_name:grafanarestart:unless-stoppedports:-"3001:3000"environment:-GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}-GF_INSTALL_PLUGINS=redis-datasourcevolumes:-grafana-data:/var/lib/grafana-./docker/grafana/dashboards:/etc/grafana/provisioning/dashboards:ronetworks:-agent-networkdepends_on:-prometheusnetworks:agent-network:driver:bridgeipam:config:-subnet:172.28.0.0/16volumes:agent-data:redis-data:postgres-data:prometheus-data:grafana-data:
2. Development Environment
# docker-compose.dev.ymlversion:'3.8'services:agent:build:context:.dockerfile:docker/agent.Dockerfiletarget:builder# Use builder stage for developmentenvironment:-NODE_ENV=development-DEBUG=agent:*,mcp:*-LOG_LEVEL=debugvolumes:-./scripts:/app/scripts:ro# Live code reload-./config:/app/config:ro-./logs:/app/logsports:-"9229:9229"# Node.js debuggercommand: ["node", "--inspect=0.0.0.0:9229", "scripts/agents/main.js"]
#!/bin/sh# docker/health-check.sh# Container health check scriptset -e
# Check if main process is runningif ! pgrep -f "node.*main.js" > /dev/null; thenecho"Main process not running"exit 1
fi# Check if health endpoint respondsif ! curl -f http://localhost:3000/health > /dev/null 2>&1; thenecho"Health endpoint not responding"exit 1
fiecho"Container healthy"exit 0
🔗 Integration with Riksdagsmonitor agentic workflows
This gh-aw skill is applied by the 11 agentic news workflows in .github/workflows/news-*.md. Their domain contract (analysis-artifact product, gate, article contract) lives in: