Production Deployment
This guide covers deploying the cliq server in a Docker container for production A2A workloads. The server runs in one container and spawns pipeline containers as siblings on the same host.
Kubernetes alternative: For horizontally scaled deployments with dynamic Job scheduling, see Kubernetes Deployment. The Docker approach below is ideal for single-host or small team deployments.
Architecture
Section titled “Architecture”Host Machine│├── Docker daemon│├── cliq-server container│ ├── Receives A2A tasks via JSON-RPC│ ├── Manages pipeline lifecycle│ └── Spawns sibling containers via Docker socket│├── cliq-pipeline-task-1 container (sibling)│ └── Runs: architect → tester → developer → reviewer│├── cliq-pipeline-task-2 container (sibling)│ └── Runs: architect → developer → reviewer│├── /data/cliq/ (settings, database, teams)│ ├── settings.json│ ├── cliq.db│ └── teams/│└── /data/workspaces/ (A2A task workspaces) ├── task-abc-123/ └── task-def-456/The server container talks to the host’s Docker daemon via the mounted /var/run/docker.sock. Pipeline containers are not nested inside the server — they are siblings managed by the same daemon.
Prerequisites
Section titled “Prerequisites”- A Linux host with Docker Engine installed (Docker Desktop works for testing but is not recommended for production)
- At least 8GB RAM (4GB for the server + headroom for pipeline containers)
- A Cursor API key for headless agent execution
- Network access for the Cursor CLI to reach Cursor’s API
Step 1: Prepare the Host
Section titled “Step 1: Prepare the Host”Create persistent directories. These must be owned by UID 1000 (the node user inside containers):
sudo mkdir -p /data/cliq /data/workspacessudo chown 1000:1000 /data/cliq /data/workspacesFind the Docker socket’s group ID (needed so the server can spawn containers):
# Linuxexport DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
# macOSexport DOCKER_GID=$(stat -f '%g' /var/run/docker.sock)Step 2: Pull the Images
Section titled “Step 2: Pull the Images”docker pull ghcr.io/elanamir/cliq-runner:latestdocker pull ghcr.io/elanamir/cliq-server:latestThe runner image is used by pipeline containers. The server image extends the runner with the Docker CLI client and kubectl (for Kubernetes Job management).
Step 3: Create Settings
Section titled “Step 3: Create Settings”Create /data/cliq/settings.json:
{ "a2a": { "agent_type": "cliq_pipeline", "instance_id": "prod-1", "workspace_root": "/data/workspaces", "public_url": "https://cliq.yourcompany.com", "meshes": [], "enable_builder": false }, "docker": { "enabled": true, "image": "ghcr.io/elanamir/cliq-runner:latest", "memory": "4g", "cpus": "2", "max_containers": 4, "env": { "CURSOR_API_KEY": "crsr_your_key_here" } }, "logging": { "server_level": "info", "core_level": "info" }}Key settings:
| Field | Purpose |
|---|---|
a2a.workspace_root | Where task workspaces are created. Must match the host mount path. |
a2a.public_url | External URL where the agent card is reachable (for mesh registration) |
docker.enabled | Must be true — each A2A task gets its own container |
docker.env.CURSOR_API_KEY | Required — agents can’t authenticate interactively in containers |
docker.max_containers | Limits concurrent pipelines. Set based on available RAM/CPU |
Step 4: Install Teams
Section titled “Step 4: Install Teams”Copy teams from a development machine:
scp -r ~/.cliqrc/teams/ prod-host:/data/cliq/teams/Or install from CliqHub (run from any machine with cliq installed):
CLIQ_HOME=/data/cliq cliq team install hub://@cliq/feature-dev-jsStep 5: Start the Server
Section titled “Step 5: Start the Server”Create a docker-compose.yml (or use the reference file from docker/docker-compose.yml in the repo):
services: cliq-server: image: ghcr.io/elanamir/cliq-server:latest restart: unless-stopped ports: - "4100:4100" group_add: - "${DOCKER_GID:-0}" volumes: - /var/run/docker.sock:/var/run/docker.sock - /data/cliq:/data/cliq - /data/workspaces:/data/workspaces environment: CLIQ_HOME: /data/cliqThe group_add grants the node user permission to access the Docker socket. DOCKER_GID should be set to the group ID of /var/run/docker.sock on the host (see Step 1).
The CURSOR_API_KEY is read from docker.env in settings.json and passed to pipeline containers automatically — it does not need to be set on the server container.
Start:
docker compose up -dVerify:
# Check the server is runningcurl http://localhost:4100/.well-known/agent-card.json
# Check logsdocker compose logs -f cliq-serverCritical: Volume Path Convention
Section titled “Critical: Volume Path Convention”Volume mount paths must be identical inside and outside the server container.
When the server calls docker run -v /data/workspaces/task-123:/data/workspaces/task-123, the Docker daemon resolves that path on the host, not inside the server container. If the paths don’t match, pipeline containers will fail to mount workspaces.
Correct:
volumes: - /data/workspaces:/data/workspaces # same path on both sidesWrong:
volumes: - /opt/cliq-data/workspaces:/data/workspaces # different paths!This applies to both /data/cliq and /data/workspaces. Always use the same source and target path.
Step 6: HTTPS and Reverse Proxy
Section titled “Step 6: HTTPS and Reverse Proxy”For production, place a reverse proxy in front of the server for TLS termination:
services: cliq-server: # ... as above, but no ports exposed to host expose: - "4100"
nginx: image: nginx:alpine ports: - "443:443" volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - /etc/letsencrypt:/etc/letsencrypt:ro depends_on: - cliq-serverExample nginx.conf:
server { listen 443 ssl; server_name cliq.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/cliq.yourcompany.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/cliq.yourcompany.com/privkey.pem;
location / { proxy_pass http://cliq-server:4100; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }}The Upgrade and Connection headers are required for WebSocket support (dashboard live updates).
Update a2a.public_url in settings to match: "https://cliq.yourcompany.com".
Step 7: Verify End-to-End
Section titled “Step 7: Verify End-to-End”Send a test A2A task:
curl -X POST https://cliq.yourcompany.com/a2a/jsonrpc \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "test-1", "method": "message/send", "params": { "message": { "messageId": "msg-1", "role": "user", "parts": [ { "kind": "text", "text": "Create a hello world Node.js application with tests" }, { "kind": "data", "data": { "team": "@cliq/feature-dev-js" } } ] }, "configuration": { "acceptedOutputModes": ["text"] } } }'Monitor:
# Server logsdocker compose logs -f cliq-server
# Pipeline containersdocker ps --filter "name=cliq-"
# Task statuscurl -X POST https://cliq.yourcompany.com/a2a/jsonrpc \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"q1","method":"tasks/get","params":{"id":"TASK_ID_FROM_RESPONSE"}}'Crash Recovery
Section titled “Crash Recovery”If the server container restarts, it recovers automatically — orphaned pipeline containers are cleaned up and interrupted tasks are marked as failed. The SQLite database and host volumes ensure no state is lost.
Monitoring
Section titled “Monitoring”Health check
Section titled “Health check”curl http://localhost:4100/.well-known/agent-card.jsonReturns the agent card if the server is healthy.
docker compose logs -f cliq-serverContainer count
Section titled “Container count”docker ps --filter "name=cliq-" --format "{{.Names}}\t{{.Status}}" | wc -lDatabase
Section titled “Database”docker compose exec cliq-server sh -c "sqlite3 /data/cliq/cliq.db 'SELECT state, COUNT(*) FROM internal_tasks GROUP BY state'"Resource Planning
Section titled “Resource Planning”| Concurrent tasks | Recommended RAM | Recommended CPU |
|---|---|---|
| 1-2 | 8 GB | 4 cores |
| 3-4 | 16 GB | 8 cores |
| 5-8 | 32 GB | 16 cores |
Each pipeline container uses up to 4GB RAM and 2 CPUs by default (configurable via docker.memory and docker.cpus). Add headroom for the server container itself (~512MB).
HUG Server
Section titled “HUG Server”If your teams use human gates, deploy the HUG server alongside — a separate service with its own Docker image that stores reviews and serves the review UI. See HUG Server for installation, Docker Compose configuration, and connecting cliq.
Troubleshooting
Section titled “Troubleshooting”Server can’t spawn containers (permission denied on docker.sock):
The node user (UID 1000) inside the container needs access to the Docker socket. Use group_add in docker-compose or --group-add in docker run to grant the socket’s group. Find the GID with stat -c '%g' /var/run/docker.sock (Linux) or stat -f '%g' /var/run/docker.sock (macOS).
Pipeline container can’t access workspace:
Verify volume paths are identical inside and outside the server container. Check that /data/workspaces exists on the host and is owned by UID 1000.
Agent authentication fails:
Confirm CURSOR_API_KEY is set in docker.env in settings.json. The server reads it from settings and passes it to pipeline containers via -e flags.
Container limit reached:
Increase docker.max_containers in settings, or wait for running tasks to complete. Monitor with docker ps --filter "name=cliq-".
Database locked:
SQLite uses WAL mode with a 5-second busy timeout. Under extreme concurrency (>10 simultaneous tasks), consider increasing busy_timeout or distributing across multiple server instances with separate databases.