Skip to main content

Migrating from Docker Compose to GetWebstack

If your project currently runs with docker compose up, you can move it to GetWebstack without rewriting your services. The two tools describe the same things — services, images, ports, volumes, environment — just with different shapes.

This guide shows the mapping field-by-field, then runs through a worked example.

Let /gws-setup do most of the work

The fastest migration is to ask an AI agent to do it for you:

/gws-setup migrate this docker-compose.yml to GetWebstack:
generate gws.json + per-service Dockerfiles + manifests, then run gws config import

The skill reads your docker-compose.yml, drafts the equivalent gws.json and Dockerfiles, validates against the live schema, and imports. Use this guide when you want to understand or audit what the agent produced.

How the two models compare

ConceptDocker ComposeGetWebstack
Unit of deploymentservice in docker-compose.ymlEntry in services[] in gws.json
Imageimage: or build:image: or dockerfile: (path to a Dockerfile)
Source codevolumes: ["./src:/app"] (bind mount)fileSync: true (sub-second bidirectional sync)
Inbound portports: ["3000:3000"]port: 3000 (Gateway routes traffic)
Localhost-only portports: ["5432:5432"]expose: { enabled: true, localPort: 5432 }
Environment varsenvironment: / env_file:Manifests (committed) + gws secret (never in gws.json)
Depends-on / orderdepends_on:Implicit — every service comes up in its own pod; readiness probes gate traffic
Hostname between servicesservice name (db)service name resolves inside the namespace
Override per environmentmultiple compose.yml filesProfiles inside one gws.json
Stateful storagevolumes: ["pgdata:/var/lib/postgresql/data"]manifests/ folder with a PersistentVolumeClaim
Managed services (Postgres, Redis, …)image: postgres:16Same — or a Helm service

Two things to note:

  • No more depends_on. GetWebstack runs every service in its own pod and uses health/readiness probes. The Gateway only routes once a service answers; nothing else needs to wait by hand.
  • Secrets stop living in your config. What was environment: { DB_PASSWORD: foo } becomes a Kubernetes Secret applied via the manifests/ folder, plus gws secret set DB_PASSWORD for values you don't want in git.

Worked example

A small Compose project — frontend, API, Postgres, Redis worker:

# docker-compose.yml
version: "3.9"
services:
web:
build: ./frontend
ports: ["80:80"]
volumes: ["./frontend:/app"]
depends_on: [api]

api:
build: ./api
ports: ["3000:3000"]
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
REDIS_URL: redis://cache:6379
volumes: ["./api:/app"]
depends_on: [db, cache]

worker:
build:
context: ./api
dockerfile: Dockerfile.worker
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
REDIS_URL: redis://cache:6379
depends_on: [db, cache]

db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes: ["pgdata:/var/lib/postgresql/data"]

cache:
image: redis:7

volumes:
pgdata:

The equivalent gws.json:

{
"name": "my-project",
"version": "1.0.0",
"services": [
{
"name": "web",
"path": "./frontend",
"port": 80,
"subdomain": "app",
"fileSync": true,
"fileSyncIgnore": ["node_modules/", "dist/"]
},
{
"name": "api",
"path": "./api",
"port": 3000,
"subdomain": "api",
"fileSync": true,
"fileSyncIgnore": ["node_modules/", "dist/"]
},
{
"name": "worker",
"path": "./api",
"dockerfile": "./api/Dockerfile.worker",
"fileSync": true
},
{
"name": "db",
"path": "./db",
"port": 5432,
"expose": { "enabled": true, "localPort": 5432 },
"helm": {
"chart": "bitnami/postgresql",
"version": "15.5.10",
"repositories": [
{ "name": "bitnami", "url": "https://charts.bitnami.com/bitnami" }
],
"values": {
"auth": {
"username": "app",
"database": "app",
"password": "${POSTGRES_PASSWORD}"
}
}
}
},
{
"name": "cache",
"path": "./cache",
"port": 6379,
"expose": { "enabled": true, "localPort": 6379 },
"helm": {
"chart": "bitnami/redis",
"version": "20.0.4",
"repositories": [
{ "name": "bitnami", "url": "https://charts.bitnami.com/bitnami" }
],
"values": { "architecture": "standalone", "auth": { "enabled": false } }
}
}
]
}

What changed:

  • build: paths → path: + (optional) dockerfile:
  • ports: ["80:80"]port: 80 for routed services
  • ports: ["5432:5432"] (DB exposed for psql) → expose: { enabled: true, localPort: 5432 }
  • volumes: ["./api:/app"] (bind mount) → fileSync: true with fileSyncIgnore
  • depends_on: — gone, replaced by readiness probes in each service's manifests/ folder
  • Hardcoded passwords moved out: see Secrets below
  • Postgres + Redis switched to Helm services so you don't maintain the manifests yourself

Secrets

Anything that was environment: { PASSWORD: ... } should leave gws.json and become a gws secret:

# Interactive prompt (recommended — no shell history)
gws secret set POSTGRES_PASSWORD --service db
gws secret set DATABASE_URL --service api
gws secret set REDIS_URL --service api

# Or read from a file in CI
gws secret set POSTGRES_PASSWORD --service db --stdin < /run/secrets/db_password

Reference them from your manifests/ envFrom block (Kubernetes injects them as env vars at deploy time). They never appear in git.

Per-environment overrides

What you used to do with docker-compose.override.yml or docker-compose.prod.yml belongs in a profile:

"profiles": {
"ci": { "services": [{ "name": "*", "fileSync": false }] },
"e2e": {
"services": [
{ "name": "*", "fileSync": false, "watch": null },
{ "name": "api", "dockerfile": "./api/Dockerfile.e2e" }
]
}
}

Then gws up --profile e2e instead of docker compose -f docker-compose.yml -f docker-compose.e2e.yml up.

Migration checklist

  1. Inventory: list every service in docker-compose.yml, plus its image/build, ports, volumes, env, and depends-on.
  2. Generate the initial gws.json — either via /gws-setup or by hand using the table above.
  3. Replace bind mounts with fileSync: true and add a fileSyncIgnore for build artifacts.
  4. Move secrets out of environment: into gws secret set and envFrom in your manifests.
  5. Swap stateful services (Postgres, MySQL, Redis, Mongo, …) to Helm services.
  6. Drop depends_on — write proper readiness probes in manifests/ instead.
  7. Map override files to profiles.
  8. Validate: gws config validate then gws config import.
  9. Run it: gws up. Open gws status and confirm every service is healthy.
  10. Compare: keep docker-compose.yml around for one or two PRs while you confirm parity, then remove it.

See also