Run Ductor locally
Bring up Ductor with Docker Compose, apply migrations, and make your first API call.
This guide stands up a complete local Ductor stack — TimescaleDB, Dragonfly, and
the Ductor binary — using the repository's docker-compose.yml, then verifies
the API is healthy and serving.
This page is the environment reference; Getting Started is the journey
This guide covers bring-up mechanics and environment detail — service names,
ports, DUCTOR_* variables, the compose stack, and the bare-binary path. If you
want the guided walkthrough that seeds a tenant, defines a workflow, and proves
durable execution by crashing a run, start with
Getting Started.
Prerequisites
- Docker and Docker Compose v2 (
docker compose, not the legacydocker-compose) - The Ductor repository checked out locally
Bring up the core stack
The compose project defines three core services:
| Service | Image | Purpose | Host port |
|---|---|---|---|
ductor | ductor:dev (built from docker/Dockerfile, development target) | The application | 8080, 50051, 9090 |
timescale | timescale/timescaledb:2.25.0-pg16 | PostgreSQL + TimescaleDB | 5433 → 5432 |
dragonfly | dragonflydb/dragonfly:v1.39.0 | Redis-compatible cache / queue / flow | 6379 |
Service names, not host names
The Postgres service is named timescale and the Redis-compatible service
is named dragonfly — there is no service called postgres or redis.
Inside the compose network, Ductor reaches them at timescale:5432 and
dragonfly:6379.
Start everything:
docker compose upCompose wires the two connection URLs and other development defaults into the
ductor service automatically:
DUCTOR_DATABASE_URL: postgres://ductor:ductor@timescale:5432/ductor?sslmode=disable
DUCTOR_CACHE_URL: redis://:ductor@dragonfly:6379
DUCTOR_DATABASE_AUTO_MIGRATE: "true" # dev convenience; run migrations explicitly in prod
DUCTOR_AUTH_ENABLED: "false" # open API for local development
DUCTOR_AUTH_ALLOW_ANONYMOUS: "true"
DUCTOR_LOG_LEVEL: info
DUCTOR_LOG_FORMAT: jsonBecause DUCTOR_DATABASE_AUTO_MIGRATE is true in compose, the database schema
is migrated automatically on first boot. In production this flag defaults to
false and you run migrations explicitly — see
Database migrations.
Run without Docker
If you have Go 1.25 and a Postgres/Redis pair already, you can run the binary
directly. Pick the dev subcommand for fast local hacking, or serve for a
production-shaped run.
The dev subcommand embeds an in-process Redis (miniredis) and forces
development-friendly defaults (auth off, anonymous allowed, auto-migrate on):
export DUCTOR_DATABASE_URL="postgres://ductor:ductor@localhost:5432/ductor?sslmode=disable"
go run ./cmd/ductor devdev still needs Postgres
ductor dev only replaces Redis with an in-process stand-in. A reachable
PostgreSQL database is still required — set DUCTOR_DATABASE_URL or pass
--database-url.
Use serve with explicit URLs and run migrations yourself first:
export DUCTOR_DATABASE_URL="postgres://ductor:ductor@localhost:5432/ductor?sslmode=disable"
export DUCTOR_CACHE_URL="redis://localhost:6379"
ductor migrate up # apply schema
ductor serve # start the serverPorts
Ductor serves on three listeners by default:
| Port | Protocol | Notes |
|---|---|---|
8080 | HTTP — REST (grpc-gateway) and Connect-RPC on the same port | server.http_addr |
50051 | gRPC | exposed by the container; api.addr defaults to :50052 for the native Connect listener |
9090 | Prometheus metrics (/metrics) | server.metrics_addr |
Verify it's healthy
The container ships a healthcheck (wget --spider http://localhost:8080/health).
You can hit the same endpoints yourself:
# process-level check via the CLI
ductor health
# liveness / readiness over HTTP
curl -s http://localhost:8080/health
curl -s http://localhost:8080/ready/health reports process liveness; /ready reports readiness (dependencies
reachable, migrations current). See
Health checks & readiness for what each probe
actually verifies and for the registered path aliases.
Explore the API
With DUCTOR_AUTH_ENABLED=false (the compose default), you can call the REST
surface without a token — every /api/... route is open on :8080. The
interactive API reference is served straight from the running instance at
http://localhost:8080/docs (Scalar), and the raw spec at
http://localhost:8080/openapi.yaml.
For a guided first request — seeding a tenant, defining a workflow, and running it end to end — follow Getting Started.
Turn on the observability stack (optional)
The compose file defines a full VictoriaMetrics-based observability stack behind
the observability profile (VictoriaMetrics, VictoriaLogs, VictoriaTraces,
vmagent, vmalert, Alertmanager, Vector, Grafana). Bring it up with:
docker compose --profile observability up
# or, if the repo Makefile is present:
make up-fullGrafana is then available at http://localhost:3000 (admin / admin by
default). See Observability for how traces,
metrics, and logs are wired.