Full-Stack Application
This example deploys a complete task management application from a single monorepo — API, frontend, background worker, managed databases, and S3 storage.
| Service | Role | Subpath |
|---|---|---|
taskflow-api | Node.js REST API | backend/ |
taskflow-frontend | Frontend web app | frontend/ |
taskflow-worker | Background job processor | worker/ |
taskflow-db | Managed PostgreSQL | — |
taskflow-cache | Managed Redis | — |
taskflow-uploads | S3 object storage bucket | — |
Step 1 — Check quota before starting
Three services require 3 × 100m CPU and 3 × 128Mi memory.
bash
cpctl quota
# Plan: basic-1-2-50 (basic)
#
# ┌──────────┬────────┬──────────┬─────┐
# │ RESOURCE │ USED │ LIMIT │ PCT │
# ├──────────┼────────┼──────────┼─────┤
# │ CPU │ 0m │ 1000m │ 0% │
# │ Memory │ 0 MiB │ 2048 MiB │ 0% │
# │ Storage │ — │ 50 GiB │ — │
# └──────────┴────────┴──────────┴─────┘Step 2 — Provision managed databases
Create Postgres and Redis in the same region the services will run in.
bash
cpctl db create postgres \
--name taskflow-db \
--region eu-west \
--size 20
# ✓ Created postgres database "taskflow-db"
# name taskflow-db
# type postgres
# host db-taskflow-db.compute-portal.svc.cluster.local
# port 5432
# region eu-west
# url postgres://admin:***@db-taskflow-db.compute-portal.svc.cluster.local:5432/taskflow-db
cpctl db create redis \
--name taskflow-cache \
--region eu-west \
--size 5
# ✓ Created redis database "taskflow-cache"
# name taskflow-cache
# type redis
# host db-taskflow-cache.compute-portal.svc.cluster.local
# port 6379
# region eu-west
# url redis://:***@db-taskflow-cache.compute-portal.svc.cluster.local:6379
cpctl db list
# ┌────────────────┬──────────┬─────────┬─────────┬──────┐
# │ NAME │ TYPE │ STATUS │ REGION │ SIZE │
# ├────────────────┼──────────┼─────────┼─────────┼──────┤
# │ taskflow-cache │ redis │ running │ eu-west │ 10GB │
# │ taskflow-db │ postgres │ running │ eu-west │ 10GB │
# └────────────────┴──────────┴─────────┴─────────┴──────┘
# Store connection URLs for later use
DB_URL=$(cpctl db url taskflow-db)
REDIS_URL=$(cpctl db url taskflow-cache)Step 3 — Create S3 bucket for file uploads
Bucket names are prefixed with a tenant ID to ensure global uniqueness.
bash
cpctl bucket create taskflow-uploads
# ✓ Bucket <tenant-id>-taskflow-uploads created
# Bucket <tenant-id>-taskflow-uploads
# Endpoint https://s3-cli.computeportal.io
# Access Key <access-key>
# Secret Key <secret-key>
# Region eu-fsn1
cpctl bucket list
# ┌──────────────────────────────────┬─────────────────────────────────┐
# │ BUCKET │ ENDPOINT │
# ├──────────────────────────────────┼─────────────────────────────────┤
# │ <tenant-id>-taskflow-uploads │ https://s3-cli.computeportal.io │
# └──────────────────────────────────┴─────────────────────────────────┘
BUCKET_ENDPOINT=https://s3-cli.computeportal.io
BUCKET_NAME=<tenant-id>-taskflow-uploads
BUCKET_ACCESS_KEY=<access-key>
BUCKET_SECRET_KEY=<secret-key>Step 4 — Deploy the API
Deploy from the backend/ subpath.
bash
cpctl deploy \
--repo https://github.com/your-org/taskflow \
--subpath backend \
--name taskflow-api \
--port 4000
# → Deploying https://github.com/your-org/taskflow...
# ✓ Deployed taskflow-api
# service taskflow-api
# url https://taskflow-api-<hash>.cpctl.app
# status building
# port 4000 (from --port flag)
#
# Building taskflow-api — job: cp-build-taskflow-api-...
# [INFO] RUN npm i
# [INFO] CMD ["npm run start"]
# [INFO] Pushed registry.example.com/cp-cli/taskflow-api:latest
# → Build complete. Service available at: https://taskflow-api-<hash>.cpctl.appStep 5 — Inject environment variables into the API
Attach the database (auto-injects DATABASE_URL and service-specific vars), then set the rest manually.
bash
# Attach Postgres — injects DATABASE_URL, POSTGRES_URL, TASKFLOW_DB_URL
cpctl db attach taskflow-db taskflow-api
# ✓ Attached taskflow-db → taskflow-api
#
# Injected into taskflow-api:
# DATABASE_URL=postgres://admin:***@db-taskflow-db.compute-portal.svc.cluster.local:5432/taskflow-db
# POSTGRES_URL=postgres://admin:***@db-taskflow-db.compute-portal.svc.cluster.local:5432/taskflow-db
# TASKFLOW_DB_URL=postgres://admin:***@db-taskflow-db.compute-portal.svc.cluster.local:5432/taskflow-db
#
# Redeploy the service:
# cpctl redeploy taskflow-api
# Set remaining env vars
cpctl env set taskflow-api \
REDIS_URL="$REDIS_URL" \
BUCKET_ENDPOINT="$BUCKET_ENDPOINT" \
BUCKET_NAME="$BUCKET_NAME" \
BUCKET_ACCESS_KEY="$BUCKET_ACCESS_KEY" \
BUCKET_SECRET_KEY="$BUCKET_SECRET_KEY" \
NODE_ENV=production
# Rolling restart to pick up all new vars
cpctl redeploy taskflow-api
# Verify
cpctl env list taskflow-api
# ┌───────────────────┬────────────────────────────────────────────────────────────┐
# │ KEY │ VALUE │
# ├───────────────────┼────────────────────────────────────────────────────────────┤
# │ DATABASE_URL │ postgres://admin:***@db-taskflow-db...svc.cluster.local/… │
# │ POSTGRES_URL │ postgres://admin:***@db-taskflow-db...svc.cluster.local/… │
# │ TASKFLOW_DB_URL │ postgres://admin:***@db-taskflow-db...svc.cluster.local/… │
# │ REDIS_URL │ redis://:***@db-taskflow-cache...svc.cluster.local:6379 │
# │ BUCKET_ENDPOINT │ https://s3-cli.computeportal.io │
# │ BUCKET_NAME │ <tenant-id>-taskflow-uploads │
# │ BUCKET_ACCESS_KEY │ <access-key> │
# │ BUCKET_SECRET_KEY │ <secret-key> │
# │ NODE_ENV │ production │
# │ PORT │ 4000 │
# └───────────────────┴────────────────────────────────────────────────────────────┘Step 6 — Deploy the frontend
The frontend needs the API URL baked in at build time via --env-var.
bash
API_URL=https://taskflow-api-<hash>.cpctl.app
cpctl deploy \
--repo https://github.com/your-org/taskflow \
--subpath frontend \
--name taskflow-frontend \
--port 3000 \
--env-var VITE_API_URL="$API_URL" \
--env-var NODE_ENV=production
# ✓ Deployed taskflow-frontend
# service taskflow-frontend
# url https://taskflow-frontend-<hash>.cpctl.app
# status building
# port 3000 (from --port flag)Step 7 — Deploy the background worker
The worker has no inbound HTTP — it polls Redis for jobs. Single replica, no port needed.
bash
cpctl deploy \
--repo https://github.com/your-org/taskflow \
--subpath worker \
--name taskflow-worker
# ✓ Deployed taskflow-worker
# service taskflow-worker
# url https://taskflow-worker-<hash>.cpctl.app
# status building
# port 3000 (injected as PORT env var)
# Attach Postgres and set Redis URL, then redeploy
cpctl db attach taskflow-db taskflow-worker
cpctl env set taskflow-worker \
REDIS_URL="$REDIS_URL" \
NODE_ENV=production
cpctl redeploy taskflow-workerStep 8 — Attach custom domains
DNS must already be configured before running these commands. Add CNAMEs pointing to cpctl.app at your DNS provider first:
tf-api.example.com CNAME cpctl.app (DNS only, not proxied)
tf-frontend.example.com CNAME cpctl.app (DNS only, not proxied)Then attach the domains:
bash
cpctl domain add taskflow-api tf-api.example.com --skip-dns
cpctl domain add taskflow-frontend tf-frontend.example.com --skip-dns
# ✓ Domain tf-api.example.com attached to taskflow-api
# domain tf-api.example.com
# service taskflow-api
# status provisioning
# tls active
#
# ✓ Domain tf-frontend.example.com attached to taskflow-frontend
# domain tf-frontend.example.com
# service taskflow-frontend
# status provisioning
# tls activeCheck TLS provisioning status (may take a few minutes):
bash
cpctl domain status taskflow-api tf-api.example.com
# status active
# tls active
# cert_verified yes
cpctl domain status taskflow-frontend tf-frontend.example.com
# status active
# tls active
# cert_verified yesStep 9 — Switch frontend to the custom API domain
bash
cpctl env set taskflow-frontend \
VITE_API_URL=https://tf-api.example.com
cpctl redeploy taskflow-frontendStep 10 — Lock down with firewall rules
bash
cpctl firewall add taskflow-api --deny 198.51.100.0/24
cpctl firewall list taskflow-api
# ┌──────────────────────┬──────┬─────────────────┐
# │ RULE │ MODE │ CIDR │
# ├──────────────────────┼──────┼─────────────────┤
# │ deny:198.51.100.0/24 │ deny │ 198.51.100.0/24 │
# └──────────────────────┴──────┴─────────────────┘Step 11 — Autoscale the API
bash
cpctl scale taskflow-api --min 2 --max 10 --cpu-threshold 70
# ✓ Autoscaling enabled for taskflow-api
# min_replicas 2
# max_replicas 10
# cpu_threshold 70%
# Worker scales manually
cpctl scale taskflow-worker --replicas 2
# ✓ Scaled taskflow-worker
# replicas 2Step 12 — Wire up CI/CD deploy hooks
bash
cpctl deploy-hook create taskflow-api
# ✓ Deploy hook created for taskflow-api
# ┌──────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────┐
# │ ID │ URL │ SECRET │
# ├──────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────┤
# │ hook_<id> │ https://api.computeportal.io/v1/hooks/hook_<id> │ <secret> │
# └──────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────┘
cpctl deploy-hook create taskflow-frontend
cpctl deploy-hook create taskflow-workerAdd to your GitHub Actions workflow:
yaml
# .github/workflows/deploy.yml
- name: Deploy API
run: |
curl -X POST ${{ secrets.CP_HOOK_API_URL }} \
-H "X-Hook-Secret: ${{ secrets.CP_HOOK_API_SECRET }}"Step 13 — Verify the full stack
bash
cpctl machine list
# ┌───────────────────┬───────────────────────────────────────────────────────────────┬─────────┬─────────┬──────────────────────────────────────────┐
# │ NAME │ IMAGE │ STATUS │ REGION │ URL │
# ├───────────────────┼───────────────────────────────────────────────────────────────┼─────────┼─────────┼──────────────────────────────────────────┤
# │ taskflow-api │ registry.example.com/cp-cli/taskflow-api:latest │ running │ eu-west │ https://tf-api.example.com │
# │ taskflow-frontend │ registry.example.com/cp-cli/taskflow-frontend:latest │ running │ eu-west │ https://tf-frontend.example.com │
# │ taskflow-worker │ registry.example.com/cp-cli/taskflow-worker:latest │ running │ eu-west │ https://taskflow-worker-<hash>.cname... │
# └───────────────────┴───────────────────────────────────────────────────────────────┴─────────┴─────────┴──────────────────────────────────────────┘
cpctl db list
# ┌────────────────┬──────────┬─────────┬─────────┬──────┐
# │ NAME │ TYPE │ STATUS │ REGION │ SIZE │
# ├────────────────┼──────────┼─────────┼─────────┼──────┤
# │ taskflow-cache │ redis │ running │ eu-west │ 10GB │
# │ taskflow-db │ postgres │ running │ eu-west │ 10GB │
# └────────────────┴──────────┴─────────┴─────────┴──────┘
cpctl bucket list
# ┌──────────────────────────────────┬─────────────────────────────────┐
# │ BUCKET │ ENDPOINT │
# ├──────────────────────────────────┼─────────────────────────────────┤
# │ <tenant-id>-taskflow-uploads │ https://s3-cli.computeportal.io │
# └──────────────────────────────────┴─────────────────────────────────┘
cpctl quota
# Plan: basic-1-2-50 (basic)
#
# ┌──────────┬─────────┬──────────┬─────┐
# │ RESOURCE │ USED │ LIMIT │ PCT │
# ├──────────┼─────────┼──────────┼─────┤
# │ CPU │ 700m │ 1000m │ 70% │
# │ Memory │ 896 MiB │ 2048 MiB │ 44% │
# │ Storage │ — │ 50 GiB │ — │
# └──────────┴─────────┴──────────┴─────┘
# Tail API logs
cpctl logs taskflow-api --tail 50
# Exec into the API pod for a quick DB check
cpctl exec taskflow-api -- node -e \
"const {Pool}=require('pg'); new Pool({connectionString:process.env.DATABASE_URL}).query('SELECT NOW()').then(r=>console.log(r.rows[0])).catch(console.error)"
# { now: 2026-08-28T11:01:49.555Z }Step 14 — Rolling redeploy and rollback
bash
# Trigger redeploy from latest main branch
cpctl redeploy taskflow-api
# View deployment history
cpctl deployment list taskflow-api
# ┌──────────┬─────────┬──────────────────────────────────────────────────────────┬─────────────────────┐
# │ ID │ STATUS │ IMAGE │ DEPLOYED AT │
# ├──────────┼─────────┼──────────────────────────────────────────────────────────┼─────────────────────┤
# │ 788f6b13 │ success │ registry.example.com/cp-cli/taskflow-api:latest │ 2026-08-28T10:59:56 │
# │ 8cd18393 │ success │ registry.example.com/cp-cli/taskflow-api:latest │ 2026-08-28T08:22:21 │
# └──────────┴─────────┴──────────────────────────────────────────────────────────┴─────────────────────┘
# Roll back if something broke
cpctl rollback taskflow-api
# ✓ Rolled back taskflow-apiTeardown
bash
cpctl delete taskflow-api --yes
cpctl delete taskflow-frontend --yes
cpctl delete taskflow-worker --yes
cpctl db destroy taskflow-db --force
cpctl db destroy taskflow-cache --force
cpctl bucket delete <tenant-id>-taskflow-uploads --yes