Intro

Dockerized Worktree Development

Run many isolated, no-AWS DevStride instances at once — one per git worktree — each with its own database, and seed each one with a different dataset using the ds worktree data tooling.

Dockerized Worktree Development

The Local Development page covers the AWS-bound loop (ds run backend + ds run ui against your personal SST stage). This page covers the fully-local, no-AWS alternative: a docker-compose stack that runs many independent DevStride instances at once — one per git worktree — on a single machine.

Each instance has its own branch, its own ports, and its own isolated dataset. They all share one set of heavy infra containers, so ~10 instances stay cheap.

Prerequisites

  • Docker Desktop must be running. The whole stack is docker-compose — there is no AWS SSO, no .env stage binding, and no sso login required for this flow.
  • Your checkout must be on a branch that contains the stack code (the docker/ scripts and the ds worktree CLI). master and develop both have it. New worktrees inherit these files from their base branch.

Mental Model

                 ┌─────────────────────────────────────────────┐
   shared core   │ postgres   dynamodb   minio   cognito-local  │   (started once)
  (devstride-core)└─────────────────────────────────────────────┘
                         ▲            ▲              ▲
        ┌────────────────┼────────────┼──────────────┼────────────────┐
        │ instance "main"│            │ instance "featx"              │ ...
        │ backend :4000  │            │ backend :4001                 │
        │ frontend :8080 │            │ frontend :8081                │
        │ soketi :6001   │            │ soketi :6002                  │
        │ db devstride_main           │ db devstride_featx            │
        │ tables main-devstride-*     │ tables featx-devstride-*      │
        │ buckets main-devstride-*    │ buckets featx-devstride-*     │
        └────────────────────────────┴───────────────────────────────┘
  • Shared core = postgres, dynamodb, minio, cognito-local. Heavy and stateful, started once, reused by every instance.
  • Per instance = a backend + frontend + tiny soketi, bind-mounting that worktree's source. Isolation inside the shared infra is logical: each instance gets its own Postgres database, its own DynamoDB tables, its own MinIO buckets, its own Cognito user pool, and its own soketi app.
  • The primary checkout is itself an instance named main (index 0, ports 4000/8080), brought up by ds run local. Extra worktrees are indexes 1, 2, 3…

Quick Start

Just the primary instance

If you only want the main checkout running locally:

ds run local                          # shared core + the "main" instance (first run: a few minutes)
ds worktree cli main migrations run   # apply schema (first time / after new migrations)
ds worktree cli main golden build     # seed the Acme golden dataset + Cognito + aggregation recalc

Open http://localhost:8080.

A sandboxed instance on its own branch

# 1) Create a new instance on its own new branch + worktree, and start it.
ds worktree create featx
#    → git worktree add, allocate ports, ensure the core is up, provision this
#      instance's data, start it detached, and print its URLs.

# 2) Apply schema + seed data (each instance starts with an EMPTY database).
ds worktree cli featx migrations run
ds worktree cli featx golden build          # optional: seed golden Acme

# 3) Open it — frontend on http://localhost:8081 (backend :4001).

# 4) See everything running.
ds worktree ls

# 5) Tear it down (stops it, drops its data + worktree; KEEPS the branch).
ds worktree rm featx

That's the whole loop.

Command Reference

CommandWhat it does
ds run localBring up the shared core + the primary main instance in the foreground.
ds worktree create <name>git worktree add a new branch, allocate ports, ensure the shared core is up, provision this instance's data, start it detached, print URLs.
ds worktree lsList instances: name, slug, index, status, frontend/backend URLs, branch.
ds worktree cli <name> <cmd...>Run any cli/commands/<cmd> inside that instance's backend (e.g. migrations run, golden build, data copy).
ds worktree logs <name> [service]Follow logs (servicebackend, frontend, soketi, bootstrap).
ds worktree up <name>Start a previously-created instance (after down).
ds worktree down <name>Stop an instance, keeping its data.
ds worktree rm <name>Stop + drop all of this instance's data (db, tables, buckets, cognito pool) + remove the worktree. Keeps the git branch; prompts if the worktree has uncommitted changes.
ds worktree core upStart just the shared core.
ds worktree core down [--wipe]Stop the shared core (--wipe also deletes all shared data volumes).

create flags

  • -b, --branch <branch> — branch to create/check out (default: the slug).
  • --from <ref> — base ref for the new branch (default: current HEAD).
  • --rebuild — rebuild the shared devstride-local image first (after dependency changes).
  • --attach — run in the foreground instead of detached.

rm flags

  • --keep-data — skip dropping the shared-core resources (just stop + remove the worktree).
  • --delete-branch — also delete the git branch (kept by default).
  • -f, --force — skip the confirmation when the worktree has uncommitted changes.

Ports

Derived from a per-instance index i (index 0 = main):

ServicePort
backend4000 + i
frontend8080 + i
soketi6001 + i

Shared core ports are fixed: postgres 5432, dynamodb 8000, minio 9000/9001, cognito-local 9229. ds worktree ls always prints the resolved URLs.

Per-Worktree Data: Pointing Each Instance at a Different Dataset

This is the whole point of the setup: every instance owns a completely separate copy of the data, so you can run several branches at once, each backed by a different dataset. The isolation is enforced at the resource level, not by convention:

LayerPer-instance resource
PostgresIts own database devstride_<slug> (the CLI patches DB_CONNECTION_STRING inside the instance's DEVSTRIDE_CONFIG secret).
DynamoDBIts own tables <slug>-devstride-{state,timetracking,ai,migrations} inside the shared -sharedDb.
MinIO (S3)Its own buckets <slug>-devstride-* (media bucket is public-read).
CognitoIts own user pool devstride-<slug> in the shared cognito-local.
RealtimeIts own soketi container + app, so channels never cross between instances — even two instances holding the same org id.

Because each instance is a self-contained database, the same data tooling from Local Development works per-instance — you just scope it with ds worktree cli <name>:

Seed a specific instance with golden Acme

ds worktree cli featx migrations run
ds worktree cli featx golden build

golden build seeds the Acme org (ac3e0000-0000-4000-8000-000000000001), creates the Cognito logins in that instance's pool, and runs the final aggregation recalc — all scoped to featx only.

Copy an org from a source DB into a specific instance

# Copy one org's data from a source database into THIS instance.
# Users land in this instance's cognito pool.
ds worktree cli featx data copy -o <organizationId>
#   At a TTY you're prompted to pick a *_CONNECTION_STRING from .env, or pass it:
ds worktree cli featx data copy -o <organizationId> --source GOLDEN_DB_CONNECTION_STRING

Import / export an instance's database

The worktree mounts its own ./data directory at /data inside the container, so dumps round-trip through there:

# Import a dump placed under the worktree's ./data.
ds worktree cli featx data import /data/<dump>.json

# Export this instance's database.
ds worktree cli featx data export /data/featx-export

Logging In

Local auth uses cognito-local, which signs in by username, not email. After a golden build, use username acme.devstride (Admin) or alex.rte (RTE persona), password Demo@123 for all personas. Each instance has its own pool, so the same credentials exist independently on every instance you seed.

Lifecycle & Teardown

  • ds worktree down <name> — stop an instance but keep its data; bring it back with ds worktree up <name>.
  • ds worktree rm <name> — stop it, drop all its data, and remove the worktree (keeps the branch; prompts on uncommitted changes).
  • ds worktree core down — stop the shared core. This stops the data layer for all instances; add --wipe to also delete the shared data volumes.

Notes & Gotchas

  • The image is built once and reused by every instance. After changing dependencies, rebuild: ds worktree create --rebuild (or ds run local --rebuild).
  • Base worktrees off a branch that has the stack. The docker/ scripts are bind-mounted from the worktree, so create instances from a branch that contains docker/instance.compose.yml et al. (master, develop, or any branch cut from them).
  • Create instances one at a time when scripting many — the CLI serializes port allocation with a lock, but back-to-back creates are simplest.
  • The instance registry lives in the repo's common git dir (.git/devstride-local/registry.json) and is shared across all worktrees.

Next Steps