diff --git a/scripts/templates/docker/Dockerfile b/scripts/templates/docker/Dockerfile new file mode 100644 index 0000000..778e497 --- /dev/null +++ b/scripts/templates/docker/Dockerfile @@ -0,0 +1,33 @@ +# Agent Factory — Docker deployment template +# Runs a Claude Code agent system in an isolated container. +# Replace {{PROJECT_NAME}} and {{ANTHROPIC_API_KEY}} with real values +# (or pass them at runtime via .env / docker compose env_file). + +FROM node:22-slim + +# Install Claude Code globally +RUN npm install -g @anthropic-ai/claude-code + +# Create a non-root agent user for security +RUN useradd -m -s /bin/bash agent + +WORKDIR /home/agent/project + +# Copy project files (adjust to your project structure) +COPY --chown=agent:agent . . + +# Set up entrypoint script +COPY --chown=agent:agent docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +USER agent + +# API key injected at runtime — never bake into image +ENV ANTHROPIC_API_KEY={{ANTHROPIC_API_KEY}} + +# Health check — entrypoint writes /tmp/agent-health on each beat +HEALTHCHECK --interval=60s --timeout=10s --start-period=30s --retries=3 \ + CMD test -f /tmp/agent-health && \ + test $(( $(date +%s) - $(date +%s -r /tmp/agent-health 2>/dev/null || echo 0) )) -lt 300 + +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/scripts/templates/docker/README.md b/scripts/templates/docker/README.md new file mode 100644 index 0000000..aab02cd --- /dev/null +++ b/scripts/templates/docker/README.md @@ -0,0 +1,77 @@ +# Docker Deployment + +Run your agent system in an isolated Docker container. + +## Prerequisites + +- Docker and Docker Compose installed +- `.env` file with your API key (see below) +- Agent system built with `/agent-factory:build` + +## Setup + +1. Copy these files to your project root: + - `Dockerfile` + - `docker-compose.yml` + - `docker-entrypoint.sh` + +2. Create `.env` in your project root: + ``` + ANTHROPIC_API_KEY=sk-ant-... + AGENT_BEAT_INTERVAL=3600 + ``` + Add `.env` to `.gitignore` — never commit API keys. + +3. Replace `{{PROJECT_NAME}}` in `docker-compose.yml` with your project name. + +## Build and run + +```bash +# Build the image +docker compose build + +# Start in background +docker compose up -d + +# View logs +docker compose logs -f + +# Stop +docker compose down +``` + +## Volume mounts + +| Host path | Container path | Purpose | +|-----------|---------------|---------| +| `./data` | `/home/agent/project/data` | Run state, outputs | +| `./memory` | `/home/agent/project/memory` | Long-term memory files | +| `./budget` | `/home/agent/project/budget` | Budget tracking | +| `./logs` | `/home/agent/project/logs` | Agent activity logs | + +These directories are created automatically on first run. + +## Environment variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `ANTHROPIC_API_KEY` | Yes | — | Your Anthropic API key | +| `AGENT_BEAT_INTERVAL` | No | `3600` | Seconds between heartbeat runs | + +## Security + +- **Never bake the API key into the image.** Always pass it via `.env` or `--env-file`. +- **Never mount the Docker socket** (`/var/run/docker.sock`) — the agent does not need Docker control. +- The container runs as a non-root `agent` user. +- `no-new-privileges:true` prevents privilege escalation. +- `restart: unless-stopped` ensures the agent recovers from crashes automatically. + +## Health check + +The entrypoint writes a timestamp to `/tmp/agent-health` on each beat. +Docker's `HEALTHCHECK` verifies this file is updated within 5 minutes. + +Check health status: +```bash +docker inspect --format='{{.State.Health.Status}}' {{PROJECT_NAME}}-agent +``` diff --git a/scripts/templates/docker/docker-compose.yml b/scripts/templates/docker/docker-compose.yml new file mode 100644 index 0000000..dbf9f60 --- /dev/null +++ b/scripts/templates/docker/docker-compose.yml @@ -0,0 +1,28 @@ +# Agent Factory — docker-compose deployment template +# Usage: docker compose up -d +# Requires: .env file with ANTHROPIC_API_KEY=... + +version: "3.8" + +services: + agent: + container_name: {{PROJECT_NAME}}-agent + build: . + restart: unless-stopped + env_file: + - .env + volumes: + # Persistent data directories — survive container restarts + - ./data:/home/agent/project/data + - ./memory:/home/agent/project/memory + - ./budget:/home/agent/project/budget + - ./logs:/home/agent/project/logs + security_opt: + - no-new-privileges:true + read_only: false + # No Docker socket mount — agent cannot control the Docker daemon + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" diff --git a/scripts/templates/docker/docker-entrypoint.sh b/scripts/templates/docker/docker-entrypoint.sh new file mode 100644 index 0000000..7a194b7 --- /dev/null +++ b/scripts/templates/docker/docker-entrypoint.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Agent Factory — Docker container entrypoint +# Validates environment, then runs the heartbeat runner in a loop. +# Bash 3.2 compatible. + +set -e + +HEALTH_FILE="/tmp/agent-health" +LOG_DIR="/home/agent/project/logs" +HEARTBEAT_SCRIPT="/home/agent/project/automation/heartbeat-runner.sh" + +# Graceful shutdown handler +shutdown_handler() { + echo "[entrypoint] SIGTERM received — shutting down gracefully" + if [ -n "$RUNNER_PID" ]; then + kill "$RUNNER_PID" 2>/dev/null || true + wait "$RUNNER_PID" 2>/dev/null || true + fi + rm -f "$HEALTH_FILE" + echo "[entrypoint] Shutdown complete" + exit 0 +} + +trap shutdown_handler TERM INT + +# Validate required environment variables +if [ -z "$ANTHROPIC_API_KEY" ]; then + echo "[entrypoint] ERROR: ANTHROPIC_API_KEY is not set" >&2 + exit 1 +fi + +# Create required directories +mkdir -p "$LOG_DIR" +mkdir -p "/home/agent/project/data" +mkdir -p "/home/agent/project/memory" +mkdir -p "/home/agent/project/budget" +mkdir -p "/home/agent/project/pipeline-output" + +echo "[entrypoint] Starting agent container at $(date)" +echo "[entrypoint] Project: $(basename /home/agent/project)" + +# Verify Claude Code is available +if ! command -v claude >/dev/null 2>&1; then + echo "[entrypoint] ERROR: claude command not found" >&2 + exit 1 +fi + +# Run heartbeat runner in loop +while true; do + # Write health check timestamp + date > "$HEALTH_FILE" + + if [ -f "$HEARTBEAT_SCRIPT" ]; then + echo "[entrypoint] Running heartbeat at $(date)" + bash "$HEARTBEAT_SCRIPT" >> "$LOG_DIR/agent.log" 2>&1 & + RUNNER_PID=$! + wait $RUNNER_PID + RUNNER_PID="" + else + echo "[entrypoint] WARNING: $HEARTBEAT_SCRIPT not found — sleeping" + fi + + # Sleep between beats (default 3600s = 1 hour) + BEAT_INTERVAL="${AGENT_BEAT_INTERVAL:-3600}" + echo "[entrypoint] Sleeping ${BEAT_INTERVAL}s until next beat" + sleep "$BEAT_INTERVAL" & + RUNNER_PID=$! + wait $RUNNER_PID + RUNNER_PID="" +done