Parallel Services Guide
Run your app's docker-compose backing services (databases, caches, Forgejo, emulators…) in a dedicated, isolated Kubernetes namespace next to your normally-deployed app, which reaches them over the cluster network.
Status: available (
020-parallel-services). The generator and isolation policies are unit-tested, and the live cluster path has been exercised on the platform EKS (for example, Forgejo running as a parallel service backingwbsp-website). It is still a young feature — see Limitations for what is and isn't supported yet.
When to use this (vs. the alternatives)
| You want… | Use |
|---|---|
| Your app deployed normally, but its compose services running beside it on the cluster | This feature (parallel services) |
| To graduate the whole app off the platform into native k8s you own | 019 hand-off (separate) |
| A tiny in-app sidecar managed by the platform | the in-platform services: idea |
Your main app keeps deploying the usual way (wbsp-app deploy --destination prod,
where prod is an aws destination). This feature only stands up the supporting
services and wires access.
How it works (the model)
- You write a small environment-definition file pointing at your existing
docker-compose.yamland listing which apps may use it. wbsp-app services upcreates a dedicated namespacewbsp-svc-‹tenant›-‹env-name›and runs each compose service there (Deployment, or StatefulSet + persistent volume if it has a named volume).- A NetworkPolicy allows only the allow-listed apps (same tenant) to reach the namespace; every other app and tenant is denied. Services are internal only (no public URL).
- When you (re)deploy an allow-listed app, the platform injects connection
environment variables into it —
‹SERVICE›_HOST,‹SERVICE›_PORT(and any${VAR}credentials) — so a config-driven app needs no code change.
What your app receives
For a compose service named forgejo listening on 3000, the app gets:
FORGEJO_HOST = forgejo.wbsp-svc-‹tenant›-‹env›.svc.cluster.local
FORGEJO_PORT = 3000(Service name uppercased, - → _.) Point your app's config at those.
Prerequisites
- Your main app deploys to the
aws(EKS) target (Lambda/aws.on-demandis not supported yet). kubectlconfigured for the platform cluster:aws eks update-kubeconfig --name ‹cluster› --region ‹region›(with your platform AWS profile).- The rebuilt
bin/wbsp-app. - NetworkPolicy enforcement on the cluster (VPC CNI network-policy controller, or Calico) — otherwise the isolation policies are inert. (Verified present on the platform EKS.)
- Durable storage (feature 021): the platform EKS provisions the EBS CSI
driver + a default
gp3StorageClass (Retain reclaim) via Terraform, so stateful services get durable persistent volumes by default.ephemeral_storageis now an explicit opt-in for genuinely disposable data only.
The environment-definition file
# wbsp-services.yaml
name: wbsp-website-backing # → namespace wbsp-svc-<tenant>-wbsp-website-backing
tenant: test
compose: ./docker-compose.yaml # path relative to THIS file
allowed_apps:
- wbsp-website # the app(s) allowed to reach these services
overrides: # optional, per compose service
postgres:
storage: managed-database # → platform-managed RDS database (the postgres default)
forgejo:
storage: persistent # → durable PVC (the default for a service with a volume)
redis:
storage: ephemeral # → emptyDir; explicitly disposableStorage policy (durable by default)
Each service has a storage policy — persistent, ephemeral, or
managed-database — set per service under overrides.‹service›.storage. The
defaults are safe (never auto-discard):
- A
postgresservice →managed-database: a platform-managed PostgreSQL database is provisioned on RDS (durable, automatically backed up), its connection is injected (‹SERVICE›_DB_HOST/_PORT/_NAME/_USER+ a‹SERVICE›_DB_PASSWORDsecret, plus a drop-in‹SERVICE›_HOST/_PORTalias), and no database pod runs in the namespace. - Any other service with a named volume →
persistent: a durable PVC on the defaultgp3class (Retain reclaim). - A service is only
ephemeralwhen you say so explicitly (or via the legacyephemeral_storage: true, which only affects volumes that declare no policy). - A non-PostgreSQL database (MySQL, MariaDB, …) is not yet offered as a managed database (v1: PostgreSQL only) — it runs durably on a persistent volume instead (never ephemeral, never blocked).
Back-compat: overrides.‹service›.run_parallel: true still works for a postgres
service (equivalent to storage: persistent — run it in the namespace on a PVC).
up prints the resolved storage decision for every service.
Compose translation (curated subset): image, ports (container port kept;
host-port maps dropped — access is in-cluster), environment, volumes
(named → persistent; ./file bind-mounts → ConfigMap), and basic settings.
Anything unsupported (profiles, custom networks, extends, build,
depends_on ordering, healthchecks) is reported as a TODO, never silently
dropped.
Credentials: literal compose values (e.g. POSTGRES_PASSWORD: wbsp) are used
as-is. Values written as ${VAR} are treated as secrets — their values are read
from your shell environment at up time and applied in-memory (never written to
disk). For those, export VAR=… before running up.
Commands
# Bring the environment up (additive: adds/updates, never removes)
wbsp-app services up --config wbsp-services.yaml
# Reconcile to the definition (also removes services you deleted; keeps their volumes)
wbsp-app services up --config wbsp-services.yaml --prune
# Stop the services but KEEP the data (volumes + managed databases retained)
wbsp-app services down --config wbsp-services.yaml
# Delete the data — takes a SNAPSHOT of every volume + managed DB first (confirm)
wbsp-app services down --config wbsp-services.yaml --purge
# Erase WITHOUT a restore point — irreversible (requires a second, typed confirm)
wbsp-app services down --config wbsp-services.yaml --purge --no-snapshotNo data loss by design: down (and --prune) never delete data. --purge is
the only destructive path and it creates an AWS-native restore point (EBS snapshot
per volume, RDS snapshot per managed database) before deleting anything — and
aborts if any snapshot fails. Only --purge --no-snapshot (separately confirmed)
performs a zero-recoverable-copy erasure. Persistent volumes use a Retain
reclaim policy, so even deleting the namespace leaves the underlying volume intact.
Changing a persistent service to ephemeral is refused unless you pass
--allow-downgrade (the existing data is retained regardless).
up prints the namespace, the allow-listed apps, the exact env vars it will
inject, and any TODOs.
Typical workflow
aws eks update-kubeconfig …wbsp-app services up --config wbsp-services.yaml- Make your app read the injected
‹SERVICE›_HOST/_PORT(config/env), then (re)deploy it so the env is injected:wbsp-app deploy --config /path/to/app/wbsp.yaml --destination prod - Verify:
kubectl -n wbsp-svc-‹tenant›-‹env› get pods,svc,netpoland confirm your app connects.
Injection is at deploy time. If you bring the environment up after the app is already running, redeploy the app to pick up the connection env.
Isolation
- Only namespaces of the allow-listed apps (matched by the
wbsp.tenant/wbsp.applabels the platform already sets) may connect. - Other apps and other tenants are denied by a default-deny + allow-list NetworkPolicy. DNS names may resolve, but connections are blocked.
- The parallel namespace may only egress DNS + within itself (it can't reach other tenants).
- ⚠️ NetworkPolicy enforcement depends on the cluster's CNI. EKS with the VPC CNI
- a policy engine (or Calico) enforces it; confirm your cluster does.
Worked example — wbsp-website + Forgejo
wbsp-website runs postgres + forgejo via compose locally. We want Forgejo
running as a parallel service that the website reaches, with Forgejo's database on
a durable, managed Postgres (RDS) and Forgejo's git repos on a durable volume.
# wbsp-website/wbsp-services.yaml
name: backing
tenant: test
compose: ./docker-compose.yml # the existing postgres + forgejo compose (.yml)
allowed_apps:
- wbsp-website
# storage policies are optional — these are also the defaults for these services:
overrides:
postgres:
storage: managed-database # Forgejo's DB → managed PostgreSQL on RDS (durable, backed up)
forgejo:
storage: persistent # Forgejo's /data (repos) → durable gp3 PVC (Retain)aws eks update-kubeconfig --name <cluster> --region ap-southeast-1
cd <your-app-repo> # e.g. the app whose services these back (wbsp-website)
wbsp-app services up --config wbsp-services.yaml
# → managed Postgres database provisioned on RDS (no postgres pod in the namespace)
# → forgejo runs in namespace wbsp-svc-test-backing on a durable PVC
# → injects FORGEJO_HOST/PORT + POSTGRES_DB_* into wbsp-website on its next deploy
wbsp-app deploy --config wbsp.yaml --destination prod # website picks up the injected envNotes for this case:
postgresdefaults to a managed database (RDS) — durable and automatically backed up; no postgres pod runs in the namespace, and Forgejo'sFORGEJO__database__*should point at the injectedPOSTGRES_DB_*connection.forgejodefaults to persistent storage, so its repos survive pod restarts.- The website reaches
forgejoatFORGEJO_HOST:FORGEJO_PORT. - Configure Forgejo via
FORGEJO__*env (don't mountapp.inias a read-only ConfigMap, which the installer can't write) and setINSTALL_LOCK=true.
Limitations (this version)
aws(EKS) only; same cluster, different namespace (separate/new cluster is a planned next phase).- Single-instance services (named volumes → one RWO replica, AZ-bound); not HA.
- Env-var credentials only (a managed secrets/rotation/config manager is future).
- Managed databases: PostgreSQL only in this version. MySQL/MariaDB and other engines run durably on a persistent volume instead (managed support for them is a later increment).
- Backups:
--purgetakes a restore point before deleting, but scheduled backups and a built-in restore/list command are a later increment (US5) — for now restore is a manual operation from the snapshot. - Ephemeral→durable migration of existing data is a documented manual step (a helper is a later increment).
- The platform does setup/teardown; ongoing lifecycle (and the cluster) is yours. Convenience features (synchronised start/stop, monitoring, log access) are future.
Troubleshooting
- App can't connect: did you redeploy the app after
services up? Check the injected env is on the app:kubectl -n wbsp-‹tenant›-‹app› set env deploy/‹app› --list | grep _HOST. - A service won't start:
kubectl -n wbsp-svc-‹tenant›-‹env› logs ‹pod›. - TODO about an unsupported compose construct: handle it manually (the
upoutput and the construct list above explain what's not translated). - Data gone after
down: only--purgedeletes volumes; plaindownretains them.