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 backing wbsp-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 clusterThis feature (parallel services)
To graduate the whole app off the platform into native k8s you own019 hand-off (separate)
A tiny in-app sidecar managed by the platformthe 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)

  1. You write a small environment-definition file pointing at your existing docker-compose.yaml and listing which apps may use it.
  2. wbsp-app services up creates a dedicated namespace wbsp-svc-‹tenant›-‹env-name› and runs each compose service there (Deployment, or StatefulSet + persistent volume if it has a named volume).
  3. 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).
  4. 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-demand is not supported yet).
  • kubectl configured 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 gp3 StorageClass (Retain reclaim) via Terraform, so stateful services get durable persistent volumes by default. ephemeral_storage is 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 disposable

Storage policy (durable by default)

Each service has a storage policypersistent, ephemeral, or managed-database — set per service under overrides.‹service›.storage. The defaults are safe (never auto-discard):

  • A postgres service → 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_PASSWORD secret, plus a drop-in ‹SERVICE›_HOST/_PORT alias), and no database pod runs in the namespace.
  • Any other service with a named volumepersistent: a durable PVC on the default gp3 class (Retain reclaim).
  • A service is only ephemeral when you say so explicitly (or via the legacy ephemeral_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-snapshot

No 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

  1. aws eks update-kubeconfig …
  2. wbsp-app services up --config wbsp-services.yaml
  3. 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
  4. Verify: kubectl -n wbsp-svc-‹tenant›-‹env› get pods,svc,netpol and 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.app labels 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 env

Notes for this case:

  • postgres defaults to a managed database (RDS) — durable and automatically backed up; no postgres pod runs in the namespace, and Forgejo's FORGEJO__database__* should point at the injected POSTGRES_DB_* connection.
  • forgejo defaults to persistent storage, so its repos survive pod restarts.
  • The website reaches forgejo at FORGEJO_HOST:FORGEJO_PORT.
  • Configure Forgejo via FORGEJO__* env (don't mount app.ini as a read-only ConfigMap, which the installer can't write) and set INSTALL_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: --purge takes 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 up output and the construct list above explain what's not translated).
  • Data gone after down: only --purge deletes volumes; plain down retains them.