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.0 → v1.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 · Status