This folder contains Docker Compose configs for the Clutch Protocol stack:
- Dev: build/run from local source with hot reload (Vite dev server).
- Stage: deployed VPS behind Cloudflare — HTTPS at the edge, plain HTTP on the origin (
docker-compose.stage.cloudflare-flex.yml).
- Docker Desktop (includes Docker Compose)
- A
.envfile in this folder (copy from.env.example)
Dev assumes you have these sibling repos next to clutch-deploy/:
../clutch-node../clutch-hub-api../clutch-hub-demo-app../clutch-hub-sdk-js
Start dev (PowerShell):
docker compose -p clutch-dev -f .\docker-compose.yml -f .\docker-compose.dev.yml up -d --buildUseful dev URLs:
- Demo app (Vite):
http://localhost:5173/ - Hub API health:
http://localhost:3000/health
Stop dev:
docker compose -p clutch-dev -f .\docker-compose.yml -f .\docker-compose.dev.yml downStage uses Nginx on the server on port 80 only (no TLS on the origin). Visitors use HTTPS via Cloudflare; Cloudflare reaches your VPS over HTTP.
What this means: Traffic is encrypted between visitors and Cloudflare. Between Cloudflare and your VPS it is unencrypted HTTP on port 80. That is acceptable for some staging setups; for production, consider Full (strict) with an origin certificate or tunnel so the last hop is also TLS.
Cloudflare dashboard
- Point your DNS A records (e.g.
stageweb,stageapi) at your server IP. - Under SSL/TLS → Overview, set encryption mode to Flexible when the origin only serves HTTP.
- Allow inbound TCP 80 on the VPS firewall (Cloudflare reaches the origin on HTTP).
Docker Compose
Use only docker-compose.yml together with docker-compose.stage.cloudflare-flex.yml. Do not add another compose overlay that also publishes node, API, or duplicate proxy ports — merges would combine port mappings and break isolation.
Start stage (PowerShell):
docker compose -p clutch-stage -f docker-compose.yml -f docker-compose.stage.cloudflare-flex.yml pull
docker compose -p clutch-stage -f docker-compose.yml -f docker-compose.stage.cloudflare-flex.yml up -d --force-recreateUse --build only when you need to rebuild an image from a local Dockerfile (e.g. the demo app). GitHub Actions deploy does not run --build; it pulls published images and recreates containers.
Stop stage:
docker compose -p clutch-stage -f docker-compose.yml -f docker-compose.stage.cloudflare-flex.yml downFiles:
- Overlay:
docker-compose.stage.cloudflare-flex.yml - Nginx:
config/nginx/nginx.stage.cloudflare-flex.conf(editserver_nameto match your hostnames)
Nginx sets X-Forwarded-Proto https so the app sees the public scheme correctly.
Frontend / SDK: The demo resolves the Hub API from the page hostname: app-stage.<domain> → https://api-stage.<domain> (GraphQL HTTP and wss://…/graphql/ws follow). The stage compose file also passes VITE_API_URL at image build time. Set ALLOWED_ORIGINS in .env to include your demo origin (e.g. https://app-stage.clutchprotocol.io) so the Hub API accepts browser requests.
Step-by-step (Ubuntu Server): docs/SSH-SERVER-SETUP.md
Workflow: .github/workflows/deploy-stage.yml runs git pull (if the deploy dir is a clone), docker compose pull, then up -d --force-recreate with the Cloudflare flex files (no --build).
Configure these in GitHub → Settings → Secrets and variables → Actions:
| Secret | Description |
|---|---|
STAGE_HOST |
VPS hostname or IP |
STAGE_USER |
SSH user (e.g. root or deploy) |
STAGE_SSH_PASSWORD |
SSH password for that user (GitHub encrypt-at-rest). Using an SSH key is more secure if you can enable key-only login on the server. |
STAGE_DEPLOY_PATH |
Absolute path to this repo on the server (e.g. /root/clutch-deploy) |
If SSH is not on port 22, add port: YOUR_PORT under with: in the workflow (or use ~/.ssh/config on a self-hosted runner).
- Clone this repo on the VPS at
STAGE_DEPLOY_PATHsogit pullupdates compose and config. - Copy
.envand ensureALLOWED_ORIGINS,JWT_SECRET, etc. exist (not stored in Git). - For private images on GHCR, run
docker login ghcr.ioonce on the server (or use a read-only PAT).
- Manual: Actions → Deploy stage (VPS) → Run workflow.
- On push to
main: whendocker-compose*.yml,config/**, or this workflow file changes. - After images publish to GHCR from
clutch-hub-demo-app,clutch-hub-api, orclutch-node: each repo’s Docker workflow can dispatchdeploy-stagehere. Add the same secretCLUTCH_DEPLOY_DISPATCH_TOKEN(a PAT that mayrepository_dispatchon clutch-deploy) to each of those repositories under Settings → Secrets and variables → Actions. Stage compose pullsghcr.io/clutchprotocol/...:latestfor node, API, and demo sodocker compose pullupdates the stack.
Pushing to clutch-hub-api / clutch-node / clutch-hub-demo-app does not run workflows in clutch-deploy by itself; the trigger-stage-deploy job (or repository_dispatch / manual run) ties them together.
- From any automation: send a
repository_dispatchwith event typedeploy-stage:
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer YOUR_PAT_WITH_REPO_SCOPE" \
https://api.github.com/repos/OWNER/clutch-deploy/dispatches \
-d '{"event_type":"deploy-stage"}'Or add a job in clutch-hub-api / clutch-node CI that calls repository_dispatch after docker push so stage updates whenever a new image is published.