The disco.json File
The disco.json file is the heart of your project’s configuration. It sits at the root of your repository and tells Disco how to build your application, which services to run, and how to route traffic.
The Basics
Section titled “The Basics”At its absolute minimum, disco.json defines a single web service and the port it listens on.
If your repository has a Dockerfile in the root, this is all you need. Disco will build the image, start the container, and route public HTTP traffic to port 8000.
{ "version": "1.0", "services": { "web": { "port": 8000 } }}web: This service name is reserved. It is the only service that automatically receives traffic from your public domain (via HTTP/HTTPS).port: The internal port your application is listening on inside the container.
Persisting Data (Volumes)
Section titled “Persisting Data (Volumes)”By default, Docker containers are ephemeral. If you restart your app or deploy a new version, any files written inside the container are lost. To keep data (like SQLite databases, user uploads, or logs), you must use Volumes.
{ "version": "1.0", "services": { "web": { "port": 8000, "volumes": [ { "name": "sqlite-data", "destinationPath": "/data" } ] } }}name: An identifier for the volume. If multiple services (e.g., web and worker) use the same name, they will share the same data.destinationPath: The folder path inside the container where this volume is mounted.
Adding Workers
Section titled “Adding Workers”Many applications need background processes to handle tasks like sending emails or processing queues. You can define additional services under the services block.
Workers usually share the same Docker image as the web service but run a different command.
{ "version": "1.0", "services": { "web": { "port": 8000, "volumes": [ { "name": "sqlite-data", "destinationPath": "/data" } ] }, "worker": { "command": "python worker.py", "volumes": [ { "name": "sqlite-data", "destinationPath": "/data" } ] } }}In this example, the worker service reuses the code built for web but overrides the startup command. Note that we also attached the sqlite-data volume so the worker can access the same database as the web server.
Cron Jobs
Section titled “Cron Jobs”Disco can run scheduled tasks automatically. Instead of keeping a container running permanently, a cron service starts, runs a command, and then exits, following a schedule.
{ "version": "1.0", "services": { "web": { "port": 8000 }, "cleanup-job": { "type": "cron", "schedule": "0 0 * * *", "command": "node cleanup.js" } }}type: Must be set to"cron".schedule: A standard 5-part cron expression (e.g.,*/10 * * * *for every 10 minutes).command: The command to execute.
Deployment Hooks
Section titled “Deployment Hooks”Sometimes you need to run a script during deployment, but before the new version of your app goes live. The most common use case is running database migrations.
Disco recognizes services starting with hook:deploy:start:before as pre-deployment tasks.
{ "version": "1.0", "services": { "web": { "port": 8000, "volumes": [{ "name": "db", "destinationPath": "/data" }] }, "hook:deploy:start:before": { "type": "command", "command": "python manage.py migrate", "volumes": [{ "name": "db", "destinationPath": "/data" }] } }}If this command fails (returns a non-zero exit code), the deployment is aborted, and the old version of your application keeps running.
Static Sites & Generators
Section titled “Static Sites & Generators”If you don’t have a backend server and just want to serve HTML/CSS/JS, you can use the static or generator types.
Pure Static Files
Section titled “Pure Static Files”If you just have a folder of files named public in your repo (no build step required):
{ "version": "1.0", "services": { "web": { "type": "static", "publicPath": "public" } }}Static Site Generators (SSG)
Section titled “Static Site Generators (SSG)”If you use tools like Astro, Next.js (static export), or Jekyll, you need to build the site first. The generator type builds your Docker image, runs it to generate files, and then serves the output.
{ "version": "1.0", "services": { "web": { "type": "generator", "publicPath": "dist" } }}publicPath: The folder inside the container where the build output is located (e.g.,dist,build,out).
Easy Mode (Pre-built Images)
Section titled “Easy Mode (Pre-built Images)”You don’t always need a Dockerfile. You can run standard software (like databases or message brokers) by specifying an image directly from Docker Hub.
{ "version": "1.0", "services": { "web": { "image": "getmeili/meilisearch:v1.13", "port": 7700, "volumes": [ { "name": "meili_data", "destinationPath": "/meili_data" } ] } }}Advanced Configuration
Section titled “Advanced Configuration”Health Checks
Section titled “Health Checks”To ensure zero-downtime (“blue green”) deployments, Disco needs to know when your app is actually ready to receive traffic. Without a specific health check, Disco will try to connect to your app’s port (8080 in the example below), but a more robust way is to define a custom command.
{ "version": "1.0", "services": { "web": { "port": 8080, "health": { "command": "curl -f http://localhost:8080/health || exit 1" } } }}Custom Dockerfiles (Monorepos)
Section titled “Custom Dockerfiles (Monorepos)”If your Dockerfile isn’t in the root, or if you have multiple services needing different Dockerfiles, use the images block.
{ "version": "1.0", "services": { "web": { "port": 8000, "image": "backend" }, "worker": { "command": "node worker.js", "image": "worker" }, }, "images": { "backend": { "dockerfile": "api/Dockerfile", "context": "api" }, "worker": { "dockerfile": "worker/Dockerfile", "context": "worker" } }}Exposing Non-HTTP Ports
Section titled “Exposing Non-HTTP Ports”If you need to expose a port for TCP/UDP traffic (e.g., for a game server or websocket service), use publishedPorts. These ports will be exposed “as is” on your server’s public IP address, and won’t be routed through HTTP/HTTPS.
{ "version": "1.0", "services": { "web": { "port": 3000, "command": "node server.cjs" }, "websocket": { "command": "npx y-websocket", "publishedPorts": [{ "publishedAs": 1234, "fromContainerPort": 1234, "protocol": "tcp" }] } }}publishedAs: The port on the host machine (public internet).fromContainerPort: The port inside the container.
Complete Reference
Section titled “Complete Reference”Here is a complex example showing multiple features in one file.
{ "version": "1.0", "services": { "web": { "port": 8000, "health": { "command": "curl -f http://localhost:8000/ || exit 1" }, "volumes": [{ "name": "data", "destinationPath": "/data" }] }, "worker": { "command": "python worker.py", "volumes": [{ "name": "data", "destinationPath": "/data" }] }, "cleanup": { "type": "cron", "schedule": "0 4 * * *", "command": "python cleanup.py" }, "hook:deploy:start:before": { "type": "command", "command": "python manage.py migrate", "volumes": [{ "name": "data", "destinationPath": "/data" }] } }}Service Fields Table
Section titled “Service Fields Table”| Field | Type | Description |
|---|---|---|
type | string | container (default), static, generator, cron, command. |
port | number | Internal port the container listens on. Required for web. |
image | string | Name of image to use. If omitted, builds from Dockerfile. |
command | string | Override the Docker CMD. |
volumes | array | List of { name, destinationPath } objects. |
schedule | string | Cron expression (only for type: "cron"). |
health | object | Object containing { command } for health checks. |
publicPath | string | Folder to serve for static or generator types. |
publishedPorts | array | Map ports to host: [{ publishedAs, fromContainerPort, protocol }]. |
exposedInternally | boolean | If true, other projects on the server can reach this service. |