Skip to content

Infrastructure as Code (cp.yaml)

This example manages a complete e-commerce platform entirely through cp.yaml files committed to the repository. Every infrastructure change goes through Git — no imperative cpctl deploy calls in the day-to-day workflow. CI/CD applies changes automatically via cpctl iac up.

ServiceRole
storefront-apiREST API (Postgres, Redis)
storefront-webCustomer-facing frontend
storefront-adminInternal admin dashboard
storefront-workerBackground order processor
storefront-dbManaged PostgreSQL
storefront-cacheManaged Redis

Repository layout

storefront/
├── api/
├── web/
├── admin/
├── worker/
└── environments/
    ├── staging/
    │   └── cp.yaml    ← staging infra definition
    └── production/
        └── cp.yaml    ← production infra definition

Each environment has its own cp.yaml. Secrets (database URLs, API keys) are never stored in cp.yaml — they are injected separately via cpctl env set and stored in the platform's secret store.


The cp.yaml files

environments/staging/cp.yaml

yaml
version: "1"

databases:
  - name: storefront-db-staging
    engine: postgres
    size_gb: 10
  - name: storefront-cache-staging
    engine: redis
    size_gb: 2

services:
  - name: storefront-api-staging
    image: ghcr.io/your-org/storefront-api:staging
    replicas: 1
    env:
      NODE_ENV: staging
      PORT: "4000"

  - name: storefront-web-staging
    image: ghcr.io/your-org/storefront-web:staging
    replicas: 1
    env:
      NODE_ENV: staging
      PORT: "3000"
      API_URL: https://storefront-api-staging-<hash>.cpctl.app

  - name: storefront-admin-staging
    image: ghcr.io/your-org/storefront-admin:staging
    replicas: 1
    env:
      NODE_ENV: staging
      PORT: "5000"
      API_URL: https://storefront-api-staging-<hash>.cpctl.app

  - name: storefront-worker-staging
    image: ghcr.io/your-org/storefront-worker:staging
    replicas: 1
    env:
      NODE_ENV: staging
    health_check:
      type: none    # worker doesn't serve HTTP — skip readiness probe

environments/production/cp.yaml

yaml
version: "1"

databases:
  - name: storefront-db
    engine: postgres
    size_gb: 50
  - name: storefront-cache
    engine: redis
    size_gb: 10

services:
  - name: storefront-api
    image: ghcr.io/your-org/storefront-api:latest
    replicas: 3
    env:
      NODE_ENV: production
      PORT: "4000"
      API_URL: https://api.storefront.example.com
    autoscaling:
      min: 3
      max: 20
      cpu: 70

  - name: storefront-web
    image: ghcr.io/your-org/storefront-web:latest
    replicas: 2
    env:
      NODE_ENV: production
      PORT: "3000"
      API_URL: https://api.storefront.example.com
    autoscaling:
      min: 2
      max: 10
      cpu: 65

  - name: storefront-admin
    image: ghcr.io/your-org/storefront-admin:latest
    replicas: 1
    env:
      NODE_ENV: production
      PORT: "5000"
      API_URL: https://api.storefront.example.com

  - name: storefront-worker
    image: ghcr.io/your-org/storefront-worker:latest
    replicas: 2
    env:
      NODE_ENV: production
    health_check:
      type: none    # worker doesn't serve HTTP — skip readiness probe

The health_check: type: none field tells the platform not to apply a TCP readiness probe. Worker-style services that don't bind a port would otherwise stay in starting indefinitely. For an imperative deploy the equivalent flag is --no-probe:

bash
cpctl deploy --image your-org/worker:latest --name my-worker --no-probe

Step 1 — First-time staging bootstrap

The --file flag lets you run iac commands from the repo root without cd.

bash
# Preview what will be created
cpctl iac up --dry-run --file environments/staging/cp.yaml

# Planned changes:
#   + create  database storefront-db-staging     engine=postgres  size_gb=10
#   + create  database storefront-cache-staging  engine=redis     size_gb=2
#   + create  storefront-api-staging     image=ghcr.io/.../storefront-api:staging     replicas=1
#   + create  storefront-web-staging     image=ghcr.io/.../storefront-web:staging     replicas=1
#   + create  storefront-admin-staging   image=ghcr.io/.../storefront-admin:staging   replicas=1
#   + create  storefront-worker-staging  image=ghcr.io/.../storefront-worker:staging  replicas=1
#             health_check: type=none
#
# Dry-run mode — no changes applied.

# Apply — databases are created first, then services
cpctl iac up --yes --file environments/staging/cp.yaml

# ✓ Created database storefront-db-staging
# ✓ Created database storefront-cache-staging
# ✓ Created storefront-api-staging
# ✓ Created storefront-web-staging
# ✓ Created storefront-admin-staging
# ✓ Created storefront-worker-staging

Step 2 — Inject secrets (one-time per environment)

Secrets are never in cp.yaml. Set them once; the platform persists them across all future redeploys. iac up merges env — it never wipes keys set outside cp.yaml.

bash
DB_URL=$(cpctl db url storefront-db-staging)
REDIS_URL=$(cpctl db url storefront-cache-staging)

cpctl env set storefront-api-staging \
  DATABASE_URL="$DB_URL" \
  REDIS_URL="$REDIS_URL" \
  STRIPE_SECRET_KEY=<your-stripe-test-key> \
  STRIPE_WEBHOOK_SECRET=<your-webhook-secret> \
  JWT_SECRET=$(openssl rand -hex 32)

cpctl env set storefront-worker-staging \
  DATABASE_URL="$DB_URL" \
  REDIS_URL="$REDIS_URL"

cpctl env set storefront-admin-staging \
  ADMIN_SECRET=$(openssl rand -hex 16)

# Rolling restart to pick up secrets
cpctl redeploy storefront-api-staging
cpctl redeploy storefront-worker-staging
cpctl redeploy storefront-admin-staging

Step 3 — Verify staging

bash
cpctl machine list
# ┌──────────────────────────┬───────────────────────────────────────┬─────────┬──────────┬─────────┬──────────────────────────────────────────────────┐
# │ NAME                     │ IMAGE                                 │ STATUS  │ REPLICAS │ REGION  │ URL                                              │
# ├──────────────────────────┼───────────────────────────────────────┼─────────┼──────────┼─────────┼──────────────────────────────────────────────────┤
# │ storefront-api-staging   │ ghcr.io/your-org/storefront-api:...   │ running │ 1 / 1    │ eu-west │ https://storefront-api-staging-<hash>.cpctl.app  │
# │ storefront-web-staging   │ ghcr.io/your-org/storefront-web:...   │ running │ 1 / 1    │ eu-west │ https://storefront-web-staging-<hash>.cpctl.app  │
# │ storefront-admin-staging │ ghcr.io/your-org/storefront-admin:... │ running │ 1 / 1    │ eu-west │ https://storefront-admin-staging-<hash>.cpctl.app│
# │ storefront-worker-staging│ ghcr.io/your-org/storefront-worker:...│ running │ 1 / 1    │ eu-west │ —                                                │
# └──────────────────────────┴───────────────────────────────────────┴─────────┴──────────┴─────────┴──────────────────────────────────────────────────┘

curl https://storefront-api-staging-<hash>.cpctl.app/health
# {"ok":true,"db":"connected","redis":"connected"}

cpctl quota
# ┌──────────┬─────────┬──────────┬─────┐
# │ RESOURCE │ USED    │ LIMIT    │ PCT │
# ├──────────┼─────────┼──────────┼─────┤
# │ CPU      │ 400m    │ 2000m    │ 20% │
# │ Memory   │ 512 MiB │ 4096 MiB │ 12% │
# │ Storage  │ —       │ 50 GiB   │  —  │
# └──────────┴─────────┴──────────┴─────┘

Step 4 — Snapshot live state as cp.yaml (optional)

If services are already running and you want to bring them under IaC, generate the initial cp.yaml from the live cluster. --no-secrets omits env keys whose names contain SECRET, KEY, PASSWORD, or TOKEN.

bash
cpctl iac init --no-secrets --file environments/staging/cp.yaml

# Snapshotting live services...
# ✓ Wrote environments/staging/cp.yaml

git add environments/staging/cp.yaml
git commit -m "chore: snapshot staging state into cp.yaml"
git push

The generated file includes replicas, non-secret env vars, and any active autoscaling config.


Step 5 — Production bootstrap (one-time)

bash
cpctl iac up --dry-run --file environments/production/cp.yaml

# Planned changes:
#   + create  database storefront-db     engine=postgres  size_gb=50
#   + create  database storefront-cache  engine=redis     size_gb=10
#   + create  storefront-api     image=ghcr.io/.../storefront-api:latest     replicas=3
#             autoscaling: min=3 max=20 cpu=70%
#   + create  storefront-web     image=ghcr.io/.../storefront-web:latest     replicas=2
#             autoscaling: min=2 max=10 cpu=65%
#   + create  storefront-admin   image=ghcr.io/.../storefront-admin:latest   replicas=1
#   + create  storefront-worker  image=ghcr.io/.../storefront-worker:latest  replicas=2
#             health_check: type=none
#
# Dry-run mode — no changes applied.

cpctl iac up --yes --file environments/production/cp.yaml

Inject production secrets the same way as staging, then attach custom domains (DNS CNAME target: cpctl.app):

bash
cpctl domain add storefront-api   api.storefront.example.com
cpctl domain add storefront-web   storefront.example.com
cpctl domain add storefront-admin admin.storefront.example.com

Step 6 — Day-to-day: promote a new image

The standard change flow:

  1. CI builds and pushes a new image tag
  2. Update the image tag in cp.yaml
  3. Open a PR → merge to main
  4. CI runs cpctl iac up — only changed services are updated

Example: API v1.3.0v1.4.0

diff
   - name: storefront-api
-    image: ghcr.io/your-org/storefront-api:v1.3.0
+    image: ghcr.io/your-org/storefront-api:v1.4.0
     replicas: 3
bash
git add environments/production/cp.yaml
git commit -m "deploy: storefront-api v1.4.0"
git push origin main

# CI output:
# Planned changes:
#   ~ update  storefront-api  image=ghcr.io/.../v1.3.0→v1.4.0
#
# ✓ Updated storefront-api

Everything else in cp.yaml — replicas, env, autoscaling — is unchanged.


Step 7 — Add a feature flag via env drift

Non-secret env vars can live in cp.yaml so they are version-controlled. Add a new key to the env: block and merge. iac up patches only the diff — secrets injected via cpctl env set are untouched.

diff
   - name: storefront-api
     env:
       NODE_ENV: production
       PORT: "4000"
       API_URL: https://api.storefront.example.com
+      FEATURE_NEW_CHECKOUT: "true"
bash
# CI output:
# Planned changes:
#   ~ update  storefront-api  env(changed)
#
# ✓ Updated storefront-api

Step 8 — Scale up for a traffic spike

Edit cp.yaml and merge — the change is tracked in Git with author and timestamp. No imperative cpctl scale commands.

diff
   - name: storefront-api
-    replicas: 3
+    replicas: 8
     autoscaling:
       min: 3
-      max: 20
+      max: 50
       cpu: 70

   - name: storefront-web
-    replicas: 2
+    replicas: 4
bash
# CI output:
# Planned changes:
#   ~ update  storefront-api  replicas=3→8  autoscaling: min=3 max=50 cpu=70%
#   ~ update  storefront-web  replicas=2→4
#
# ✓ Updated storefront-api
# ✓ Updated storefront-web

Scale back down after the event — edit, commit, merge.


Step 9 — Drift detection

--dry-run shows whether live state has drifted from cp.yaml. Run it on a schedule (e.g. nightly) to catch manual changes made outside of Git.

bash
cpctl iac up --dry-run --file environments/production/cp.yaml

# Clean — no drift:
# → Everything is up to date — no changes needed.

# Drift detected (e.g. someone ran cpctl scale storefront-api --replicas 12):
# Planned changes:
#   ~ update  storefront-api  replicas=12→3

Step 10 — Roll back a bad deploy

Roll back is a Git revert — no platform-specific commands needed.

bash
git revert HEAD
git push origin main

# CI output:
# Planned changes:
#   ~ update  storefront-api  image=ghcr.io/.../v1.4.0→v1.3.0
#
# ✓ Updated storefront-api

Step 11 — Tear down staging to save quota

cpctl iac down destroys every service and database listed in cp.yaml. Useful for turning off staging overnight.

bash
cpctl iac down --yes --file environments/staging/cp.yaml

# Resources to destroy:
#   - service   storefront-api-staging
#   - service   storefront-web-staging
#   - service   storefront-admin-staging
#   - service   storefront-worker-staging
#   - database  storefront-db-staging (postgres)
#   - database  storefront-cache-staging (redis)
#
# ✓ Deleted service storefront-api-staging
# ✓ Deleted service storefront-web-staging
# ✓ Deleted service storefront-admin-staging
# ✓ Deleted service storefront-worker-staging
# ✓ Deleted database storefront-db-staging
# ✓ Deleted database storefront-cache-staging

# Bring it back up any time
cpctl iac up --yes --file environments/staging/cp.yaml

CI/CD integration (GitHub Actions)

yaml
# .github/workflows/deploy-production.yml
name: Deploy Production

on:
  push:
    branches: [main]
    paths:
      - environments/production/cp.yaml

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install cpctl
        run: curl -fsSL https://install.computeportal.io | sh

      - name: Plan
        env:
          CP_API_TOKEN: ${{ secrets.CP_API_TOKEN }}
        run: |
          cpctl login --token "$CP_API_TOKEN"
          cpctl iac up --dry-run --file environments/production/cp.yaml

      - name: Apply
        env:
          CP_API_TOKEN: ${{ secrets.CP_API_TOKEN }}
        run: cpctl iac up --yes --file environments/production/cp.yaml
yaml
# .github/workflows/deploy-staging.yml
name: Deploy Staging

on:
  push:
    branches: [staging]
    paths:
      - environments/staging/cp.yaml

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install cpctl
        run: curl -fsSL https://install.computeportal.io | sh
      - name: Apply staging infrastructure
        env:
          CP_API_TOKEN: ${{ secrets.CP_API_TOKEN }}
        run: |
          cpctl login --token "$CP_API_TOKEN"
          cpctl iac up --yes --file environments/staging/cp.yaml
yaml
# .github/workflows/drift-check.yml
name: Drift Check

on:
  schedule:
    - cron: "0 3 * * *"   # 03:00 UTC nightly

jobs:
  drift:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install cpctl
        run: curl -fsSL https://install.computeportal.io | sh
      - name: Check production drift
        env:
          CP_API_TOKEN: ${{ secrets.CP_API_TOKEN }}
        run: |
          cpctl login --token "$CP_API_TOKEN"
          cpctl iac up --dry-run --file environments/production/cp.yaml

With this setup:

  • Merging to staging → staging environment updates automatically
  • Merging to main → production environment updates automatically
  • Every infrastructure change has a Git commit, author, and timestamp
  • Rollback is git revert — no platform-specific commands needed
  • Nightly drift check surfaces manual changes made outside of Git

What lives in cp.yaml vs. what doesn't

Concerncp.yamlcpctl env set
Image tag
Replica count
Autoscaling policy
Health check type
Feature flags
Database URLs✓ (secret)
API keys / tokens✓ (secret)
JWT secrets✓ (secret)
Webhook signing keys✓ (secret)

GPU Compute Platform