Multi-Provider Example¶
A single configuration deploying services to both AWS EKS and Docker via SSH. Shared credentials and configuration are centralized in the custom block using ${self:custom.*} references.
pctl.yaml¶
name: acme
custom:
region: us-east-1
cluster: acme-prod
namespace: production
ecr_base: "507738123456.dkr.ecr.us-east-1.amazonaws.com"
ghcr_base: "ghcr.io/acme-org"
ghcr_user: "${env:GHCR_USER}"
ghcr_token: "${env:GHCR_TOKEN}"
db_url: "${ssm:/acme/prod/db-url}"
services:
# --- AWS EKS services ---
api:
image: ./services/api/Dockerfile
registry: "${self:custom.ecr_base}/api"
command: "node dist/server.js"
env:
NODE_ENV: production
PORT: "3000"
DB_URL: "${self:custom.db_url}"
scale:
replica: [2, 20]
cpu: 500m
memory: 1Gi
ports:
- "8080:3000"
health:
interval: 15
command: "curl -sf http://localhost:3000/health"
retries: 5
onfailure: restart
volumes:
- path: /app/uploads
provider:
name: aws
options:
cluster: "${self:custom.cluster}"
namespace: "${self:custom.namespace}"
strategy: RollingUpdate
serviceAccount: api-sa
rbac:
- resources: ["secrets"]
verbs: ["get"]
storage:
size: 50Gi
worker:
image: ./services/worker/Dockerfile
registry: "${self:custom.ecr_base}/worker"
env:
DB_URL: "${self:custom.db_url}"
QUEUE_URL: "${ssm:/acme/prod/queue-url}"
scale:
replica: 5
cpu: 1
memory: 2Gi
provider:
name: aws
options:
cluster: "${self:custom.cluster}"
namespace: "${self:custom.namespace}"
# --- Docker SSH services ---
monitor:
image: ./services/monitor/Dockerfile
registry:
url: "${self:custom.ghcr_base}/monitor"
username: "${self:custom.ghcr_user}"
password: "${self:custom.ghcr_token}"
env:
GRAFANA_ADMIN_PASS: "${env:GRAFANA_PASS, admin}"
scale:
replica: 1
cpu: 256m
memory: 512Mi
ports:
- "3001:3000"
provider:
name: docker
options:
host: 10.0.1.50
user: deploy
key: ~/.ssh/deploy_key
sudo: true
redis:
image: redis:7-alpine
scale:
replica: 1
ports:
- "6379:6379"
volumes:
- path: /data
health:
interval: 5
command: "redis-cli ping"
retries: 3
onfailure: restart
provider:
name: docker
Explanation¶
Shared Configuration¶
The custom block centralizes repeated values:
ecr_baseis reused by both AWS services for their registry.ghcr_base,ghcr_user,ghcr_tokenare reused by the Docker SSH service.db_urlis resolved once from SSM and referenced by multiple services.clusterandnamespaceare shared across AWS services.
Different Registries Per Provider¶
- AWS services use ECR (
507738...ecr...). ECR auto-authenticates using AWS credentials. - Docker SSH service uses GHCR (
ghcr.io/acme-org/monitor) with explicit username/password from environment variables. - Redis uses a pre-built image, no registry needed.
Provider Mix¶
api and worker deploy to an EKS cluster. monitor deploys to a remote server via SSH. redis runs locally. All share the same state file (pctl.acme.json).
Deploy¶
Output:
[aws] deployed "acme-api"
[aws] deployed "acme-worker"
[docker] started "acme-monitor"
[docker] deployed "acme-monitor" (1 replica)
[docker] started "acme-redis"
[docker] deployed "acme-redis" (1 replica)
State File (pctl.acme.json)¶
{
"acme-api": {
"provider": "aws",
"cluster": "acme-prod",
"namespace": "production",
"registryUrl": "507738123456.dkr.ecr.us-east-1.amazonaws.com/api",
"image": "507738123456.dkr.ecr.us-east-1.amazonaws.com/api:1710000001",
"labels": { "managed-by": "pctl", "pctl-stack": "acme", "pctl-service": "acme-api" },
"fingerprint": "a1b2c3...",
"hasPorts": true,
"hasHpa": true,
"hasRbac": true,
"hasPvc": true,
"hasPv": false,
"hasPullSecret": false,
"pushedByPctl": true
},
"acme-worker": {
"provider": "aws",
"cluster": "acme-prod",
"namespace": "production",
"registryUrl": "507738123456.dkr.ecr.us-east-1.amazonaws.com/worker",
"image": "507738123456.dkr.ecr.us-east-1.amazonaws.com/worker:1710000002",
"labels": { "managed-by": "pctl", "pctl-stack": "acme", "pctl-service": "acme-worker" },
"fingerprint": "d4e5f6...",
"hasPorts": false,
"hasHpa": false,
"hasRbac": false,
"hasPvc": false,
"hasPv": false,
"hasPullSecret": false,
"pushedByPctl": true
},
"acme-monitor": {
"provider": "docker",
"host": "10.0.1.50",
"user": "deploy",
"key": "~/.ssh/deploy_key",
"sudo": true,
"registryUrl": "ghcr.io/acme-org/monitor",
"image": "ghcr.io/acme-org/monitor:1710000003",
"labels": { "managed-by": "pctl", "pctl-stack": "acme", "pctl-service": "acme-monitor" },
"fingerprint": "g7h8i9...",
"replica": 1,
"hasPorts": true,
"pushedByPctl": true
},
"acme-redis": {
"provider": "docker",
"host": "local",
"image": "redis:7-alpine",
"labels": { "managed-by": "pctl", "pctl-stack": "acme", "pctl-service": "acme-redis" },
"fingerprint": "j0k1l2...",
"replica": 1,
"hasPorts": true,
"pushedByPctl": false
}
}
Destroy¶
[aws] destroyed "acme-api"
[aws] destroyed "acme-worker"
[aws] namespace "production" removed
[docker] destroyed "acme-monitor"
[docker] destroyed "acme-redis"
AWS resources (deployments, services, HPA, PVC, RBAC, namespace) are cleaned up via Kubernetes API. Docker containers and volumes are removed locally and via SSH.