Skip to content

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.

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.

  • 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

Create persistent directories. These must be owned by UID 1000 (the node user inside containers):

Terminal window
sudo mkdir -p /data/cliq /data/workspaces
sudo chown 1000:1000 /data/cliq /data/workspaces

Find the Docker socket’s group ID (needed so the server can spawn containers):

Terminal window
# Linux
export DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
# macOS
export DOCKER_GID=$(stat -f '%g' /var/run/docker.sock)
Terminal window
docker pull ghcr.io/elanamir/cliq-runner:latest
docker pull ghcr.io/elanamir/cliq-server:latest

The runner image is used by pipeline containers. The server image extends the runner with the Docker CLI client and kubectl (for Kubernetes Job management).

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:

FieldPurpose
a2a.workspace_rootWhere task workspaces are created. Must match the host mount path.
a2a.public_urlExternal URL where the agent card is reachable (for mesh registration)
docker.enabledMust be true — each A2A task gets its own container
docker.env.CURSOR_API_KEYRequired — agents can’t authenticate interactively in containers
docker.max_containersLimits concurrent pipelines. Set based on available RAM/CPU

Copy teams from a development machine:

Terminal window
scp -r ~/.cliqrc/teams/ prod-host:/data/cliq/teams/

Or install from CliqHub (run from any machine with cliq installed):

Terminal window
CLIQ_HOME=/data/cliq cliq team install hub://@cliq/feature-dev-js

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/cliq

The 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:

Terminal window
docker compose up -d

Verify:

Terminal window
# Check the server is running
curl http://localhost:4100/.well-known/agent-card.json
# Check logs
docker compose logs -f cliq-server

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 sides

Wrong:

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.

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-server

Example 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".

Send a test A2A task:

Terminal window
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:

Terminal window
# Server logs
docker compose logs -f cliq-server
# Pipeline containers
docker ps --filter "name=cliq-"
# Task status
curl -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"}}'

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.

Terminal window
curl http://localhost:4100/.well-known/agent-card.json

Returns the agent card if the server is healthy.

Terminal window
docker compose logs -f cliq-server
Terminal window
docker ps --filter "name=cliq-" --format "{{.Names}}\t{{.Status}}" | wc -l
Terminal window
docker compose exec cliq-server sh -c "sqlite3 /data/cliq/cliq.db 'SELECT state, COUNT(*) FROM internal_tasks GROUP BY state'"
Concurrent tasksRecommended RAMRecommended CPU
1-28 GB4 cores
3-416 GB8 cores
5-832 GB16 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).

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.

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.