Skip to content

Patterns

// Patternssnippets

Short, copy-ready FlowLayer config fragments for one situation at a time — a single readiness probe, one dependsOn chain, one oneshot. Adapt names, commands, and ports to your stack.

These pages cover common orchestration shapes with when-to-use guidance, why they work, and where they break:

Use a TCP probe when a dependency is ready as soon as it accepts connections. This is the clean default for databases, brokers, and non-HTTP infra.

{
"services": {
"postgres": {
"cmd": ["docker", "compose", "up", "postgres"],
"stopCmd": ["docker", "compose", "down", "postgres"],
"port": 5432,
"ready": { "type": "tcp", "port": 5432 }
}
}
}

Use an HTTP probe when the service exposes a health endpoint and startup is only complete after app boot. This keeps downstream services blocked until the app is actually responding.

{
"services": {
"api": {
"cmd": ["node", "dist/server.js"],
"port": 3001,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3001/health"
}
}
}
}

Use dependsOn to turn service relationships into deterministic startup waves. Each dependency must reach its terminal startup state before the next service starts.

{
"services": {
"postgres": {
"cmd": ["docker", "compose", "up", "postgres"],
"ready": { "type": "tcp", "port": 5432 }
},
"migrate": {
"cmd": ["pnpm", "--dir", "./services/users", "run", "db:migrate"],
"kind": "oneshot",
"dependsOn": ["postgres"]
},
"users": {
"cmd": ["pnpm", "--dir", "./services/users", "run", "start:dev"],
"port": 3002,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3002/health"
},
"dependsOn": ["migrate"]
}
}
}

Mark setup steps as oneshot when they are expected to finish and unlock downstream services. This is the right fit for migrations, seed jobs, or code generation.

{
"services": {
"seed": {
"cmd": ["pnpm", "--dir", "./services/users", "run", "db:seed"],
"kind": "oneshot",
"dependsOn": ["postgres"]
}
}
}

Model queue consumers and background workers as normal long-running services. They stay visible in the same runtime and can depend on infra plus APIs.

{
"services": {
"billing-worker": {
"cmd": ["pnpm", "--dir", "./services/billing", "run", "start:worker"],
"dependsOn": ["postgres", "users-api"],
"env": {
"QUEUE_NAME": "billing-jobs"
}
}
}
}

Use string form for simple argv splitting and array form whenever quoting or spaces matter. If you need shell features like &&, invoke a shell explicitly in the array.

{
"services": {
"safe-array": {
"cmd": ["sh", "-c", "cd backend && pnpm start"]
},
"simple-string": {
"cmd": "pnpm --dir ./frontend dev"
}
}
}

Attach service-local environment with env when the process needs explicit runtime configuration. Those values are merged into the parent environment for both cmd and stopCmd.

{
"services": {
"users-api": {
"cmd": ["pnpm", "--dir", "./services/users", "run", "start:dev"],
"env": {
"PORT": "3002",
"DATABASE_URL": "postgres://postgres:postgres@127.0.0.1:5432/users"
},
"port": 3002,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3002/health"
}
}
}
}

Point each service at its package root instead of wrapping the whole repo in one command. This keeps logs, restart controls, and dependency edges readable per service.

{
"services": {
"billing": {
"cmd": ["pnpm", "--dir", "./services/billing", "run", "start:dev"],
"port": 3003,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3003/health"
}
},
"users": {
"cmd": ["pnpm", "--dir", "./services/users", "run", "start:dev"],
"port": 3002,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3002/health"
}
}
}
}

FlowLayer works well when infra comes from Docker while app services run natively. Use explicit stop commands for Docker services and normal process commands for the rest.

{
"services": {
"redis": {
"cmd": ["docker", "compose", "up", "redis"],
"stopCmd": ["docker", "compose", "down", "redis"],
"port": 6379,
"ready": { "type": "tcp", "port": 6379 }
},
"api": {
"cmd": ["go", "run", "./cmd/api"],
"dependsOn": ["redis"],
"port": 8080,
"ready": {
"type": "http",
"url": "http://127.0.0.1:8080/health"
}
}
}
}

Use stopCmd when normal signal-based shutdown is not enough to tear down the underlying resource. This is especially useful for Docker Compose services or custom cleanup steps.

{
"services": {
"python-stack": {
"cmd": ["docker", "compose", "-f", "./docker/python.compose.yml", "up"],
"stopCmd": ["docker", "compose", "-f", "./docker/python.compose.yml", "down"]
}
}
}

Long-running watch processes fit naturally as daemon services. Keep the command explicit so restarts and logs stay tied to the same package.

{
"services": {
"billing": {
"cmd": ["pnpm", "--dir", "./services/billing", "run", "start:dev"],
"port": 3003,
"ready": {
"type": "http",
"url": "http://127.0.0.1:3003/health"
}
}
}
}

When one dependency starts slowly, let the rest of the graph stay blocked instead of racing manually. The readiness gate becomes the single unlock point for the whole downstream chain.

{
"services": {
"search": {
"cmd": ["docker", "compose", "up", "search"],
"port": 9200,
"ready": { "type": "tcp", "port": 9200 }
},
"indexer": {
"cmd": ["pnpm", "--dir", "./services/indexer", "run", "start"],
"dependsOn": ["search"]
},
"api": {
"cmd": ["pnpm", "--dir", "./services/api", "run", "start:dev"],
"dependsOn": ["indexer"]
}
}
}

Bring up Kafka first, then start producers and consumers that wait on real infra readiness. Models a local event-driven stack with explicit boot ordering.

{
"services": {
"kafka": {
"cmd": "docker compose up kafka",
"stopCmd": "docker compose down kafka",
"ready": { "type": "tcp", "port": 9092 }
},
"api": {
"cmd": "pnpm --dir ./apps/api dev",
"port": 3001,
"dependsOn": ["kafka"],
"env": {
"KAFKA_BROKERS": "localhost:9092"
}
},
"worker": {
"cmd": "go run ./cmd/worker",
"dependsOn": ["kafka"],
"env": {
"KAFKA_BROKERS": "localhost:9092"
}
}
}
}

Run disposable tooling in a container before booting the app. A reproducible one-shot gate keeps local workflows clean and consistent.

{
"services": {
"go-test": {
"kind": "oneshot",
"cmd": [
"docker", "run", "--rm",
"-v", "./:/workspace",
"-w", "/workspace",
"golang:1.24",
"go", "test", "./..."
]
},
"api": {
"cmd": "go run ./cmd/api",
"dependsOn": ["go-test"],
"port": 8080,
"ready": {
"type": "http",
"url": "http://localhost:8080/healthz"
}
}
}
}

Python buffers stdout by default - output won’t appear in FlowLayer until the buffer flushes. Pass -u or set PYTHONUNBUFFERED=1 to stream logs in real time.

{
"services": {
"worker": {
"cmd": "python -u worker.py"
}
}
}

Or via environment variable:

{
"services": {
"worker": {
"cmd": "python worker.py",
"env": { "PYTHONUNBUFFERED": "1" }
}
}
}

Wait for a dependency that lives outside FlowLayer - a shared database, a remote queue, or a service on another machine. A shell wrapper probes the TCP port in a loop, then hands off.

{
"services": {
"api": {
// Polls port 5432 until the external DB accepts connections, then starts
"cmd": [
"sh", "-c",
"until nc -z localhost 5432; do sleep 1; done && node dist/server.js"
],
"port": 3001
}
}
}

Need full walkthroughs, the AI Prompt Builder, or larger reference snippets? Open Examples.