Multi-arch (amd64, arm64), signed container images for Conductor OSS, the open-source workflow orchestration engine originally built at Netflix. Server and UI in one image.
Community-maintained by upstreamyard. This is not an official Conductor image. Conductor is © its authors and licensed under Apache-2.0 (license text in
/licenses/conductor/LICENSEinside the image).
| Registry | Image |
|---|---|
| Docker Hub | upstreamyard/conductor |
| GitHub Container Registry | ghcr.io/upstreamyard/conductor |
Tags: latest, <major>.<minor> (e.g. 3.32), <version> (e.g. 3.32.5, matching the Conductor releases). Only stable releases are built: release candidates (-rc) never get a tag here, and latest always points to the newest stable release. New releases are built automatically within 24 hours.
| Ports | 8080 API and Swagger UI (/swagger-ui/index.html), 5000 UI (nginx, proxies /api to the server) |
| Health | GET /health on 8080. It reports the process only, not the database |
| Metrics | GET /actuator/prometheus on 8080 |
| Requires | nothing for a quick trial (built-in SQLite). For production: PostgreSQL (tested with postgres:16), or one of Conductor's other backends |
| Runs as | non-root UID 10001 |
| Migrations | automatic on startup |
| Default auth | none. Anyone who can reach ports 8080 or 5000 can create and run workflows |
AI agents and coding assistants: read AGENTS.md for deployment rules, and llms.txt for an index of all docs.
Try it without any dependencies (data is stored in SQLite inside the container and lost when it is removed):
docker run -d -p 8080:8080 -p 5000:5000 upstreamyard/conductor:latest
curl http://localhost:8080/healthOpen the UI at http://localhost:5000 and the API docs at http://localhost:8080/swagger-ui/index.html.
With PostgreSQL, using our Compose file:
curl -O https://raw.githubusercontent.com/upstreamyard/conductor/main/docker-compose.yml
docker compose up -dYour workers (your own code, in any language with a Conductor SDK) connect to http://<host>:8080/api.
Conductor is a Spring Boot application, so every property can be set as an environment variable: upper-case, with . and - replaced by _ (conductor.db.type becomes CONDUCTOR_DB_TYPE). PostgreSQL for persistence, queues and search:
docker run -d -p 8080:8080 -p 5000:5000 \
-e CONDUCTOR_DB_TYPE=postgres \
-e CONDUCTOR_QUEUE_TYPE=postgres \
-e CONDUCTOR_INDEXING_ENABLED=true \
-e CONDUCTOR_INDEXING_TYPE=postgres \
-e CONDUCTOR_ELASTICSEARCH_VERSION=0 \
-e CONDUCTOR_EXTERNAL_PAYLOAD_STORAGE_TYPE=postgres \
-e SPRING_DATASOURCE_URL=jdbc:postgresql://your-postgres:5432/conductor \
-e SPRING_DATASOURCE_USERNAME=conductor \
-e SPRING_DATASOURCE_PASSWORD=... \
upstreamyard/conductor:latestAs in the Conductor project's own image (conductoross/conductor), CONFIG_PROP selects a properties file in /app/config. Mount your own file there:
docker run -d -p 8080:8080 -p 5000:5000 \
-v ./conductor.properties:/app/config/conductor.properties:ro \
-e CONFIG_PROP=conductor.properties \
upstreamyard/conductor:latestThe image also contains the Conductor project's example files (config-postgres.properties, config-redis-os.properties, …). They expect the host names from the Conductor project's Compose files (postgresdb, rs, os) and default passwords, so for real deployments use your own file or environment variables.
Two things to know (both tested):
- The config file wins over environment variables. This is how Conductor itself works: values from
CONFIG_PROPare loaded as Java system properties, which take precedence. Don't set the same property in both places. - A missing file stops the container. If
/app/config/$CONFIG_PROPdoesn't exist, the container exits withERROR: CONFIG_PROP=..., but /app/config/... does not exist. The Conductor project's own image instead starts silently with the built-in SQLite defaults, which ignores your database.
| Variable | Default in image | Notes |
|---|---|---|
CONFIG_PROP |
– | Properties file name in /app/config |
JAVA_OPTS |
empty | JVM options, e.g. -Xmx2g or -XX:MaxRAMPercentage=75 |
SERVER_PORT |
8080 |
The UI on 5000 proxies to localhost:8080, so keep the default if you use the UI |
OPENAI_API_KEY, ANTHROPIC_API_KEY, … |
– | Optional keys for Conductor's LLM tasks. Without them the server logs cannot init ... model errors at startup. These are harmless |
All properties and their defaults: the Conductor project's application.properties. Like the Conductor project's own image, the server is built with the Elasticsearch 7 client. OpenSearch and Elasticsearch 8 need a different build of the server and aren't included. Tested here: SQLite (default) and PostgreSQL for persistence, queues and indexing.
A Helm chart for this image is in preparation in upstreamyard/helm-charts. Until then, the facts you need for your own manifests:
- The container runs as non-root UID
10001and works withrunAsNonRoot: true. - Use
GET /healthon port8080for liveness and readiness. It doesn't check the database. - Run one replica for now. Conductor's default workflow lock is
local_only, also in the Conductor project's PostgreSQL example config, so several replicas aren't safe without a distributed lock. Multi-replica setups will be tested and documented with the Helm chart. - Pass passwords as environment variables from a Secret (
SPRING_DATASOURCE_PASSWORD), not in a config file.
Images are signed keylessly with cosign and include an SBOM and SLSA provenance:
cosign verify upstreamyard/conductor:latest \
--certificate-identity-regexp '^https://github.com/upstreamyard/conductor/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
docker buildx imagetools inspect upstreamyard/conductor:latest --format '{{ json .SBOM }}'.github/workflows/build.yml checks out the Conductor release tag and builds Dockerfile with the same Gradle command as the Conductor project's release workflow. Before publishing, scripts/smoke-test.sh runs on native amd64 and arm64 runners. It starts the image against PostgreSQL, runs a workflow end to end, checks the UI and its /api proxy, and checks that a missing config file is rejected. Nothing is published unless both architectures pass. Everything is public and reproducible.
Same layout as the Conductor project's own image conductoross/conductor (docker/server/Dockerfile): server on 8080, nginx with the UI on 5000, CONFIG_PROP, files under /app. The differences:
- runs as non-root UID
10001instead of root; tinias PID 1, and Java gets stop signals, sodocker stopshuts down cleanly instead of being killed after the timeout;- logs go to stdout only (the Conductor project's image also writes
/app/logs/server.log); - a
CONFIG_PROPfile that doesn't exist stops the container with an error, instead of falling back to SQLite; - base images pinned by digest instead of
debian:stable-slim; - release candidates are never tagged. The Conductor project's
latestsometimes points to an RC.
Issues with the image: open an issue here. Issues with Conductor itself: conductor-oss/conductor.
