Get your docker compose app live.
run.zero does not read docker-compose.yml or build Dockerfiles. You translate the file into a run-zero.yaml: a buildpack builds your code, the database becomes an addon, ready-made images start directly.
Needs the Developer plan, the free plan only covers websites. Pricing · Check the limits first
- One service per container
- Managed Postgres
- Ready-made images work
> Deploy the docker-compose folder to run.zero.
runzero_create_appdoneexample-docker-compose
runzero_push_filesdoneone commit
runzero_deploydoneservices and Postgres created
runzero_wait_for_builddonelive
live at https://web-example-docker-compose-da4587ce.run-zero.de
- Live demoRunning on run.zero(opens in a new tab)
- Example codeMIT, on GitHub(opens in a new tab)
- BuildpacksNode, Python, Go, PHP, Java
- Hosted in GermanyHetzner, Nuremberg
From compose to live in four steps.
Tested with the example that runs live.
- 01
Sort your containers
Containers with
build:become services withpath:. Containers with a ready-madeimage:stay that way. Your Postgres container becomes an addon. - 02
Set the port
Each service listens on the port from the file, 8080 by default. For a ready-made image, set the port the image listens on.
- 03
Add run-zero.yaml
The file sits at the root of the repo.
attachTonames the services that getDATABASE_URL.run-zero.yaml version: "1"services: web: path: ./web port: 8080addons: db: type: postgres attachTo: [web] - 04
Create an account and deploy
Create an account, connect your agent with this command, then sign in from Claude Code with
/mcpand Authenticate.- Then tell your agent: deploy this repo to run.zero.
Without an agent
Create the app in the dashboard and connect your GitHub repo. Every push rebuilds.
Connect Claude Code claude mcp add --transport http \ -s user run-zero \ https://api.run-zero.de/mcp \ --client-id runzero-cli \ --callback-port 53761
After 1 to 3 minutes the app is live at https://web-<app>.run-zero.de. Check a route of your app, in the example /api/health. If the build is red, runzero_get_build_logs tells you why.
One app, two descriptions.
docker compose stays for local work, the other file runs it on run.zero.
Your local setup, unchanged. run.zero ignores this file.
Example on GitHub(opens in a new tab)# Local only. On run.zero, run-zero.yaml applies.services: web: build: ./web ports: - "8080:8080" environment: PORT: "8080" DATABASE_URL: postgres://app:app@db:5432/app depends_on: - db db: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: app POSTGRES_DB: app volumes: - db-data:/var/lib/postgresql/datavolumes: db-data:web builds from ./web, whoami runs a ready-made image, db becomes the Postgres addon.
version: "1"services: web: path: ./web port: 8080 whoami: image: docker.io/traefik/whoami:v1.10 port: 8080 env: WHOAMI_PORT_NUMBER: "8080"addons: db: type: postgres attachTo: [web]Reads DATABASE_URL and counts visits. Locally from compose, on run.zero from the addon.
// Web service of the compose example. Locally DATABASE_URL comes from docker-compose.yml, on run.zero from the postgres addon.const express = require('express');const { Pool } = require('pg');const port = process.env.PORT || 8080;const url = process.env.DATABASE_URL;// run.zero: the addon URL carries sslmode=require against a self-signed cert, so drop it and pass ssl explicitly.const onPlatform = !!url && url.includes('sslmode=');const pool = url ? new Pool({ connectionString: onPlatform ? url.replace(/[?&]sslmode=[^&]*/, '') : url, ssl: onPlatform ? { rejectUnauthorized: false } : false, }) : null;async function countVisit() { await pool.query('CREATE TABLE IF NOT EXISTS visits (id serial PRIMARY KEY, at timestamptz DEFAULT now())'); await pool.query('INSERT INTO visits DEFAULT VALUES'); const { rows } = await pool.query('SELECT count(*)::int AS n FROM visits'); return rows[0].n;}const app = express();app.get('/', async (req, res) => { const visits = pool ? await countVisit() : 'no database attached'; res.type('html').send(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>docker compose on run.zero</title></head><body style="font-family:system-ui,sans-serif;max-width:640px;margin:64px auto;padding:0 24px"><h1>docker compose on run.zero</h1><p>The web service came from ./web, the database from the postgres addon. Visits stored in Postgres: <strong id="visits">${visits}</strong></p><p><a href="/api/health">/api/health</a></p></body></html>`);});app.get('/api/health', (req, res) => res.json({ status: 'ok', database: !!pool, platform: 'run.zero' }));app.listen(port, () => console.log(`listening on ${port}`));Started with npm start. The Dockerfile next to it is for compose only.
{ "name": "run-zero-example-compose-web", "version": "1.0.0", "private": true, "engines": { "node": "22.x" }, "scripts": { "start": "node server.js" }, "dependencies": { "express": "^4.21.2", "pg": "^8.13.1" }}What your compose file turns into.
Line by line, as in the example.
| docker-compose.yml | run-zero.yaml |
|---|---|
| services.web.build: ./web | path: ./web, built by a buildpack |
| services.x.image: name:tag | image: name:tag, the whoami service in the example |
| ports: "8080:8080" | port: 8080, public at https://<service>-<app>.run-zero.de |
| environment: | env: on the service, secret values as secrets in the dashboard |
| services.db.image: postgres:16 | becomes an addon, currently version 18, not selectable |
| volumes: | - path: /pb_data, optional size: 20 (default 10 GiB, cannot change later), one volume per service |
| depends_on: | dropped, every service starts on its own |
What does not work today.
Developer allows 2 services per app.
Needs the Developer plan
Free has no server apps and no Postgres, a deploy stops with an error.
See pricingNo Dockerfile builds
The Dockerfile is silently ignored: no
RUN apt-get, no multi-stage builds. With anindex.htmland no manifest the repo becomes a static page.Volumes: one instance
Without
volumes:, local data is gone after a restart. With a volume it runs exactly one instance, with a short gap on deploy.Only Postgres and Keycloak
Postgres runs version 18, not selectable. No Redis, no MongoDB, no dump import.
See all limits
What you stop doing yourself.
Builds, HTTPS, the database and backups run on the platform.
Data stays in Germany
Servers and backups at Hetzner in Nuremberg. With a data processing agreement.
HTTPS by default
Every service gets an address on
run-zero.de, custom domains a Let’s Encrypt certificate.Postgres as an addon
One entry in
run-zero.yaml,DATABASE_URLarrives as an environment variable.No lock-in
Your code stays in your repo, the database is a dump you can download.
Questions.
About docker compose.
My repo only has a Dockerfile. Now what?
Build the image yourself, for example in your CI, push it to a registry and reference it with image:. Code in Node, Python, Go, PHP or Java also works without a Dockerfile through the buildpack.
Does run.zero read my docker-compose.yml?
No, the platform only reads run-zero.yaml. The compose file can stay in the repo for local development.
What happens to my Postgres container?
It becomes the managed Postgres addon. The platform sets DATABASE_URL plus PGHOST, PGPORT, PGUSER, PGPASSWORD and PGDATABASE in every service listed in attachTo.
How does existing data get from my volume into the addon?
There is no dump import. Single SQL statements go through the SQL console or runzero_db_query. We have not tested a full move or extensions.
Can I run SQLite or PocketBase as an image?
Yes, with volumes: on the service, from the Developer plan. In our PocketBase example a record survived a redeploy and scaling to 0. Without volumes:, local data is gone on restart.
What applies to a service with a volume?
It runs exactly one instance and cannot scale higher, and scaling to 0 keeps the data. A deploy has a short gap, preview and manual publishing do not work, and a volume cannot be resized yet.
Why does my image crash on a variable like PB_PORT?
Kubernetes sets variables like <NAME>_PORT=tcp://… for every service. A service named pb collides with PocketBase’s own PB_PORT. Name the service pocketbase, for example.
What about Redis or MongoDB?
Neither can be declared as an addon in run-zero.yaml today. For caches and queues, use Postgres or an external provider for now.