Skip to content

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.

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.


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.

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.


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.

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.


If you don’t have a backend server and just want to serve HTML/CSS/JS, you can use the static or generator types.

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"
}
}
}

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).

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"
}
]
}
}
}

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"
}
}
}
}

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"
}
}
}

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.

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" }]
}
}
}
FieldTypeDescription
typestringcontainer (default), static, generator, cron, command.
portnumberInternal port the container listens on. Required for web.
imagestringName of image to use. If omitted, builds from Dockerfile.
commandstringOverride the Docker CMD.
volumesarrayList of { name, destinationPath } objects.
schedulestringCron expression (only for type: "cron").
healthobjectObject containing { command } for health checks.
publicPathstringFolder to serve for static or generator types.
publishedPortsarrayMap ports to host: [{ publishedAs, fromContainerPort, protocol }].
exposedInternallybooleanIf true, other projects on the server can reach this service.