Containerization & CI/CD
Docker Compose behind Traefik, and the live GitHub Actions pipeline that ships the frontend + api to the VPS through ghcr on every push to main.
Every component ships as a container, orchestrated by Docker Compose behind the existing Traefik. The frontend and api now redeploy automatically on every push to main — built on GitHub runners, shipped through the GitHub Container Registry, pulled by the VPS.
main triggers a build, a push to ghcr.io, and a zero-touch redeploy on the box. The Convex schema deploy stays a deliberate manual step (see below). See VPS foundation (live) for the full running stack.Continuous deployment (live)
Two workflows split the work. ci.yml validates every push and PR; deploy.yml reacts to a successful CI run on main (via workflow_run) so PR builds never deploy and a deploy is never cancelled mid-flight.
- Push to mainA conventional commit lands on the default branch.
- CI gateLint, type-check, tests, build, gitleaks + audit. No green, no deploy.
- Build & pushFrontend + api images build on GitHub runners → ghcr.io, tagged :latest and :<sha>.
- Ship to the VPSdeploy.sh is copied over SSH; the box pulls the immutable :<sha> image.
- Swap & verifyRetag to the running :latest, compose up -d, Traefik health-checks the new container.
Build off the box, not on it
The VPS is small (the frontend is capped near half a core). Building a whole pnpm workspace there competed with the running stack, so the build moved to GitHub runners. Images are pushed to ghcr.io/clawlas/clawlas-frontend and …/clawlas-api, tagged both :latest and the commit :<sha> — the SHA tag is immutable, which is what makes rollback deterministic.
The VPS only pulls
No git clone and no Node toolchain live on the box. deploy.sh pulls the SHA-pinned image and retags it to the local tag the compose file already references, so nothing on the VPS has to change for a deploy:
docker pull ghcr.io/clawlas/clawlas-frontend:<sha>
docker tag ghcr.io/clawlas/clawlas-frontend:<sha> clawlas-frontend:latest
# …same for the api image, then:
docker compose up -d frontend apiGated on green CI, authenticated without a stored token
The deploy job authenticates to ghcr transiently with the workflow's built-in GITHUB_TOKEN — there is no Personal Access Token to mint or rotate on the VPS. Secret scanning runs the gitleaks binary directly, not the action (which now needs a paid licence for organisation repos).
| Name | Kind | Purpose |
|---|---|---|
| VPS_SSH_HOST / _USER / _KEY | Secret | SSH onto the VPS with a dedicated deploy key |
| VITE_CONVEX_URL / _SITE_URL / VITE_SITE_URL | Variable | Public client URLs baked into the frontend bundle at build time |
| GITHUB_TOKEN | Built-in | Pushes the images and authorises the VPS pull — nothing to manage |
deploy/scripts/deploy-backend.sh from a machine with Node.Rollback is one command
Because every release is a distinct :<sha> image, rolling back is re-running deploy.sh against an earlier commit (or re-running the Deploy workflow on it):
FRONTEND_IMAGE=ghcr.io/clawlas/clawlas-frontend:<previous-sha> \
API_IMAGE=ghcr.io/clawlas/clawlas-api:<previous-sha> \
bash /opt/clawlas/deploy/scripts/deploy.shServices
- frontend
- convex
- fastapi
- redis
- agent-worker
- quant-worker
- memory-worker
- backtest-worker
- execution-engine
- risk-engine
- scheduler
- security-scanner
- sellability-tester
- feature-engine
- position-monitor
- prometheus
- grafana
- loki
- portainer
- backup
Each has a multi-stage Dockerfile, pinned base images and no secrets baked in. The runtime image is profile-agnostic — config selects what a container runs. Today the frontend, api, Convex, Redis and the monitoring stack are live; the trading workers below are the planned target.
Early-token collectors (kept separate)
One failing source must never stop discovery, so each collector is its own container:
- collector-cex-listings
- collector-cex-market-data
- collector-solana-launches
- collector-evm-contracts
- collector-dex-pools
- collector-launchpads
- collector-liquidity
- collector-wallet-activity
- collector-social
Redeploying the trading runtime is safety-critical
App containers are stateless and swap freely. The trading runtime is not: while real capital is in play, a redeploy follows a stricter sequence.
- 1Graceful shutdownOn SIGTERM the runtime stops new entries, flushes in-flight work, checkpoints SQLite (WAL), then exits.
- 2Exchange-native stopsIn production, stops live as exchange-side orders so a restart never leaves a position unprotected.
- 3Reconciliation on bootOn startup the runtime reconciles against the real exchange account before acting — no phantom or duplicate positions.
Environments
| Environment | Trading |
|---|---|
| Local | Compose, synthetic/historical data, isolated Convex, no real keys |
| VPS staging | Production-like, shadow & paper only, no real trading permissions |
| VPS production | Real credentials, restricted execution network, manual deploy, separate volumes/secrets |