From d4f768705afebb8dd497a108acea551c193ece84 Mon Sep 17 00:00:00 2001 From: Amit Paz Date: Fri, 19 Jun 2026 15:03:08 +0300 Subject: [PATCH] harden: externalize secrets, pin images, add healthcheck + CI smoke test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Security: - Remove the committed real-format LORE_API_KEY plus the dev ADMIN_API_KEY / JWT_SECRET from docker-compose.yml; read them from a gitignored .env (${VAR:?...} so compose refuses to start if unset). Add .env.example + .gitignore. NOTE: the previously-committed lore_sk_ key must be revoked on the Lore side — it is in git history and should be treated as compromised. Reproducibility / correctness: - Pin all four images off :latest (agentlens 0.12.2, agentgate 0.12.1, lore 1.1.1, agentkit-mesh 1.3.0). - Add a healthcheck to agentgate (node-based probe; the image has no curl) and gate agentlens on agentgate + mesh being healthy, so startup is deterministic. - Fix the README/compose contradiction (it claimed Lore/Mesh build locally; all four actually pull from Docker Hub) and document the .env step. CI: - Add a smoke-test workflow: docker compose config, up --wait (health-gated), poll the four documented /health endpoints, down -v. The repo previously had no CI verifying the stack composes. Verified locally: all 5 containers report healthy and all four health endpoints return 200. Co-Authored-By: Claude Opus 4.8 (1M context) --- .env.example | 15 +++++++++++++++ .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + README.md | 24 ++++++++++++++++-------- docker-compose.yml | 25 ++++++++++++++++++------- 5 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ae442a8 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Copy this file to .env and set real values: cp .env.example .env +# .env is gitignored — never commit real secrets. +# +# These are LOCAL-DEV placeholders. Generate fresh values for any +# non-local use (e.g. `openssl rand -hex 32`). + +# API key AgentLens uses to call the Lore service. Must match a key the +# Lore instance accepts. +LORE_API_KEY=lore_sk_localdev_replace_me + +# AgentGate admin API key. +ADMIN_API_KEY=agentkit-local-dev-admin-key + +# AgentGate JWT signing secret. +JWT_SECRET=agentkit-local-dev-jwt-secret-change-me diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e3a369f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + smoke: + name: Compose smoke test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Seed .env from example + run: cp .env.example .env + + - name: Validate compose config + run: docker compose config -q + + - name: Bring the stack up (wait for healthchecks) + run: docker compose up -d --wait --wait-timeout 180 + + - name: Poll documented health endpoints + run: | + set -e + curl -fsS http://localhost:3000/api/health/overview > /dev/null + curl -fsS http://localhost:3002/health > /dev/null + curl -fsS http://localhost:8765/health > /dev/null + curl -fsS http://localhost:8766/health > /dev/null + echo "All four services healthy." + + - name: Dump logs on failure + if: failure() + run: docker compose logs --no-color + + - name: Tear down + if: always() + run: docker compose down -v diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md index ff40fb3..e4184e2 100644 --- a/README.md +++ b/README.md @@ -18,26 +18,31 @@ ```bash git clone https://github.com/agentkitai/agentkit-stack.git cd agentkit-stack +cp .env.example .env # then edit .env and set real secrets docker compose up -d ``` -AgentLens and AgentGate pull from Docker Hub; Lore and Mesh build locally. +All four services pull pinned images from Docker Hub. Secrets are read from +`.env` (gitignored); `docker compose up` will refuse to start until the +required keys in `.env` are set. ## Services | Service | Port | Image | Description | |------------|------|-------|-------------| -| AgentLens | 3000 | [`pazgaz/agentlens`](https://hub.docker.com/r/pazgaz/agentlens) | Observability dashboard | -| AgentGate | 3002 | [`pazgaz/agentgate`](https://hub.docker.com/r/pazgaz/agentgate) | Approval gateway | -| Lore | 8765 | *(built from source)* | Semantic memory (pgvector) | +| AgentLens | 3000 | [`pazgaz/agentlens:0.12.2`](https://hub.docker.com/r/pazgaz/agentlens) | Observability dashboard | +| AgentGate | 3002 | [`pazgaz/agentgate:0.12.1`](https://hub.docker.com/r/pazgaz/agentgate) | Approval gateway | +| Lore | 8765 | [`pazgaz/lore:1.1.1`](https://hub.docker.com/r/pazgaz/lore) | Semantic memory (pgvector) | | Lore DB | — | `pgvector/pgvector:pg16` | PostgreSQL + pgvector | -| Mesh | 8766 | *(built from source)* | Agent discovery registry | +| Mesh | 8766 | [`pazgaz/agentkit-mesh:1.3.0`](https://hub.docker.com/r/pazgaz/agentkit-mesh) | Agent discovery registry | ## Docker Hub Images ```bash -docker pull pazgaz/agentlens # ~1GB (dashboard + server) -docker pull pazgaz/agentgate # ~241MB (approval gateway) +docker pull pazgaz/agentlens:0.12.2 # dashboard + server +docker pull pazgaz/agentgate:0.12.1 # approval gateway +docker pull pazgaz/lore:1.1.1 # semantic memory +docker pull pazgaz/agentkit-mesh:1.3.0 # agent discovery registry ``` ## Health Checks @@ -51,7 +56,10 @@ curl http://localhost:8766/health # Mesh ## Rebuild from Source -To build AgentLens/AgentGate locally instead of pulling from Docker Hub, uncomment the `build:` lines in `docker-compose.yml` and remove the `image:` lines, then: +By default every service pulls a pinned image from Docker Hub. To build any of +them from a local checkout instead, uncomment that service's `build:` lines in +`docker-compose.yml` (each expects the sibling repo checked out alongside this +one), then: ```bash docker compose up -d --build diff --git a/docker-compose.yml b/docker-compose.yml index bc39308..7131252 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: agentlens: - image: pazgaz/agentlens:latest + image: pazgaz/agentlens:0.12.2 # build: # context: ../agentlens # dockerfile: Dockerfile @@ -16,7 +16,7 @@ services: - LORE_ENABLED=true - LORE_MODE=remote - LORE_API_URL=http://lore:8765 - - LORE_API_KEY=lore_sk_a01079a5b4b6c6f194b6419a69ff92dd + - LORE_API_KEY=${LORE_API_KEY:?set LORE_API_KEY in .env (copy from .env.example)} - MESH_ENABLED=true - MESH_URL=http://mesh:8766 volumes: @@ -27,13 +27,17 @@ services: depends_on: lore: condition: service_healthy + agentgate: + condition: service_healthy + mesh: + condition: service_healthy deploy: resources: limits: memory: 256M agentgate: - image: pazgaz/agentgate:latest + image: pazgaz/agentgate:0.12.1 # build: # context: ../agentgate # dockerfile: Dockerfile @@ -44,21 +48,28 @@ services: - PORT=3002 - NODE_ENV=production - LOG_FORMAT=json - - ADMIN_API_KEY=agentkit-local-dev-key-1234 - - JWT_SECRET=agentkit-local-dev-jwt-secret-1234567890abcdef + - ADMIN_API_KEY=${ADMIN_API_KEY:?set ADMIN_API_KEY in .env (copy from .env.example)} + - JWT_SECRET=${JWT_SECRET:?set JWT_SECRET in .env (copy from .env.example)} - AGENTLENS_URL=http://agentlens:3000 volumes: - agentgate-data:/app/data networks: - agentkit restart: unless-stopped + healthcheck: + # node-based probe: the agentgate image (node:22-alpine) has no curl + test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3002/health',r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))\""] + interval: 5s + timeout: 3s + retries: 10 + start_period: 10s deploy: resources: limits: memory: 256M lore: - image: pazgaz/lore:latest + image: pazgaz/lore:1.1.1 # build: # context: ../lore # dockerfile: Dockerfile.server @@ -109,7 +120,7 @@ services: memory: 128M mesh: - image: pazgaz/agentkit-mesh:latest + image: pazgaz/agentkit-mesh:1.3.0 # build: # context: ../agentkit-mesh # dockerfile: Dockerfile