DockerSince v1.0.0
Introduction
The Docker provider stops idle containers when their session expires, and starts them again when a request needs them. It can also pause them, or keep them running with throttled resources. Sablier runs as a container next to them and calls the Docker Engine API through the Docker socket. With Docker Compose, it starts a container’s depends_on dependencies first.
Install Sablier
Run Sablier as a Compose service, with the Docker socket mounted:
# Sablier with the Docker provider. The other files in this folder add
# workloads or options to it with extra -f flags, for example:
# docker compose -f compose.yml -f whoami.yml up -d sablier
name: demo
services:
sablier:
image: sablierapp/sablier:1.19.0 # x-release-please-version
restart: unless-stopped
command:
- start
- --provider.name=docker
volumes:
# Sablier starts and stops containers through the Docker API.
- /var/run/docker.sock:/var/run/docker.sock
ports:
- "10000:10000"docker compose up -d sablierdocker is the default provider, so --provider.name=docker is optional.
Reverse proxy plugins reach Sablier at http://sablier:10000 from a container on the same Compose network.
If the connection to the daemon drops, Sablier reconnects on its own. It waits one more second after each failed attempt, up to 30 seconds.
Usage example
Opt a container in with the sablier.enable label, and give it a group:
services:
whoami:
image: sablierapp/mimic:v0.3.3
command: ["-running-after=1s", "-healthy=true", "-healthy-after=2s"]
restart: unless-stopped
# Sablier reports the container ready once this health check passes.
healthcheck:
test: ["CMD", "/mimic", "healthcheck"]
interval: 1s
timeout: 3s
retries: 10
labels:
- sablier.enable=true
- sablier.group=whoamiAdd it to the Compose project that runs Sablier, and create the container without starting it:
docker compose -f compose.yml -f whoami.yml create whoamiThen configure your reverse proxy to call Sablier for the whoami group in front of the whoami service. On the first request, Sablier starts the container and holds the request, or shows a waiting page, until its health check passes. When the session expires, Sablier stops it.
docker compose up, docker start or a restart policy, keeps running until its first session expires. To avoid that, stop externally-started instances.Provider reference
Configuration
| Option | Default | Description |
|---|---|---|
--provider.docker.strategy | stop | stop or pause idle containers. See Pause instead of stop. Since v1.11.0. |
--provider.docker.honor-restart-policy | false | Report a one-shot container that exited 0 as completed. See One-shot containers. Deprecated: the default in v2. Since v1.15.0. |
Every --provider.* option that is not provider-specific also applies; see the CLI reference. Sablier connects to the daemon with the Docker environment variables. With tracing on, every Docker API call becomes a span, with no daemon configuration.
Workloads
Containers
Sablier starts and stops containers that already exist:
- Stop runs the equivalent of
docker stop, then waits for the container to exit. Docker sends the stop signal, then kills the container after its stop grace period. - Start runs the equivalent of
docker start. A container that is already running is left alone.
Sablier never creates, recreates or removes a container. After docker compose down, create the containers again.
Give managed containers restart: unless-stopped or restart: "no". With always, Docker restarts a container Sablier stopped when the daemon restarts.
Pause instead of stopSince v1.11.0
Set --provider.docker.strategy=pause to pause idle containers instead of stopping them:
# Pause idle containers instead of stopping them. Compose replaces the whole
# command of compose.yml with this one.
services:
sablier:
command:
- start
- --provider.name=docker
- --provider.docker.strategy=pause- Stop frees the container’s CPU and memory. The next request waits for the application to boot.
- Pause freezes the container’s processes and keeps their memory. The next request resumes them instantly.
The strategy applies to every container. A container that is stopped rather than paused is started as usual.
Docker Compose
Compose sets these labels on every container it creates:
com.docker.compose.project: the project name.com.docker.compose.service: the service name.com.docker.compose.depends_on: the service’s dependencies, asservice:condition:restartentries.
Sablier reads them to start a container’s dependencies. A container created without Compose has no dependencies.
Start dependencies togetherSince v1.15.0
Declare the order with depends_on. When a request starts a container, Sablier starts its dependencies first:
# Run with honor-restart-policy.yml, so that the migration can complete:
# docker compose -f compose.yml -f honor-restart-policy.yml -f depends-on.yml up -d sablier
services:
app:
image: sablierapp/mimic:v0.3.3
command: ["-running-after=1s", "-healthy=true", "-healthy-after=1s"]
restart: unless-stopped
labels:
- sablier.enable=true
- sablier.group=app
depends_on:
db:
condition: service_healthy
migration:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "/mimic", "healthcheck"]
interval: 1s
# Labeled: Sablier starts it first and stops it with the group.
db:
image: sablierapp/mimic:v0.3.3
command: ["-running-after=2s", "-healthy=true", "-healthy-after=2s"]
restart: unless-stopped
labels:
- sablier.enable=true
- sablier.group=app
healthcheck:
test: ["CMD", "/mimic", "healthcheck"]
interval: 1s
# One-shot: runs once and exits 0. Never label it.
migration:
image: sablierapp/mimic:v0.3.3
command: ["-running=false", "-exit-code=0"]
restart: "no"
depends_on:
db:
condition: service_healthyA request for the app group starts db and waits until it is healthy. It then starts migration and waits until it has completed. Only then does it start app. Sablier waits for each condition:
| Condition | Sablier waits until the dependency is |
|---|---|
service_started (default) | running, or has completed |
service_healthy | running and healthy, or running without a health check |
service_completed_successfully | completed: exited 0 under a non-restarting policy. Requires the restart policy option. |
Decide whether to label each dependency:
- Dependencies need no labels to start. Sablier starts every dependency a container declares, opted in or not.
- Sablier only stops opted-in containers. Give a dependency
sablier.enable=trueand the same group to stop it with the group. Leave it unlabeled to keep it running, for example a database that other apps share. - Never label a one-shot container. A session is ready only when every member is ready, and a container that exits by design never is.
- A dependency that already meets its condition is left alone. Sablier doesn’t restart a running database, and doesn’t run a completed migration again.
How Sablier resolves dependencies
- Sablier looks for dependencies in the same Compose project only. It skips the dependencies of a container without a project label, and logs a warning.
- It skips a dependency service that has no container, and logs a warning.
- When a service has several containers, it picks a running one, otherwise the first name in alphabetical order.
- It resolves dependencies of dependencies, and starts independent ones in parallel.
- If the graph has a cycle, it ignores the dependencies, logs a warning, and starts the container alone.
- A dependency that exits with an error, or is dead, fails the start.
- It ignores the
restartoption ofdepends_on. - The whole start, dependencies included, must finish within five minutes.
- A dependency written without a condition, by older Compose versions, must be running, healthy or not, like Compose does. A one-shot that already exited does not count. Any unknown condition behaves like
service_started.
One-shot containersSince v1.15.0
A one-shot container, such as a migration, runs once and exits 0. For a service_completed_successfully dependency on it to resolve, set --provider.docker.honor-restart-policy:
# Report a one-shot container that exited 0 as completed, so a
# service_completed_successfully dependency can resolve. Compose replaces
# the whole command of compose.yml with this one.
services:
sablier:
command:
- start
- --provider.name=docker
- --provider.docker.honor-restart-policy=true # deprecated: default in v2With it, Sablier reads the container’s restart policy when it exits:
| Exit code | Restart policy | Status |
|---|---|---|
0 | no or on-failure | completed: the job is done. |
0 | always or unless-stopped | stopped: Sablier starts it again on demand. |
Not 0 | any | error |
Without the option, every container that exits 0 is stopped. The condition never resolves, and the start times out.
- A completed container doesn’t run again. Later requests find it completed and skip it, until you recreate it.
- Give long-running containers
restart: unless-stopped. Docker reports a missing restart policy asno, so a container without one looks completed once Sablier stops it, if it exits0.
--provider.docker.honor-restart-policy is deprecated. It will be removed in v2, where honoring the restart policy becomes the default.Instance names
A reverse proxy can target one container by name instead of a group. The whoami container of the usage example is named:
demo-whoami-1The name is the container’s name. Compose names containers <project>-<service>-<index>, unless you set a container_name.
Prefer groups: a name ties your proxy configuration to the project name.
Labels
Sablier reads labels on the container. In Compose, set them under the service’s labels, as a list or a map:
services:
team-api:
labels:
- sablier.enable=true
- sablier.group=team-a,team-b # one container in two groupssablier.enablemust betrue. Sablier lists containers with the filtersablier.enable=true.- Labels are fixed when the container is created. Docker can’t change them afterwards, so recreate the container after you edit them.
- Don’t use
deploy.labels. Compose sets them on the service only, not on its containers.
The label reference lists every key.
Workload settings
Each excerpt below comes from a Compose file in examples/docker. Add it to compose.yml with another -f flag.
EnableSince v1.4.0
sablier.enable=true opts the container in. It is required, and any other value is ignored. See the usage example.
GroupSince v1.4.0
sablier.group puts the container in one or more comma-separated groups. A session for any of them starts it. Without it, the container is in the default group.
services:
team-api:
labels:
- sablier.enable=true
- sablier.group=team-a,team-b # one container in two groupsReady afterSince v1.13.0
sablier.ready-after keeps reporting the container as not ready for a settling delay after its health check first passes. Use it when the container passes its health check before it can serve traffic.
services:
slow-starter:
labels:
- sablier.enable=true
- sablier.group=slow-starter
- sablier.ready-after=15s # wait 15s after the health check first passesLearn more about delaying readiness.
Ready on startSince v1.15.0
sablier.ready-on-start=true reports the container ready as soon as Sablier starts it, without waiting for its health check.
services:
worker:
labels:
- sablier.enable=true
- sablier.group=worker
- sablier.ready-on-start=true # ready as soon as the start is sentLearn more about marking ready on start.
Running hoursSince v1.13.0
sablier.running-hourskeeps the container running during a daily window.sablier.running-dayslimits that window to some weekdays, since v1.15.0.
services:
office-app:
labels:
- sablier.enable=true
- sablier.group=office-app
- sablier.running-hours=09:00-18:00
- sablier.running-days=Mon,Tue,Wed,Thu,FriThe window uses Sablier’s local time. The Sablier image defaults to UTC: set the TZ environment variable to change it.
# sablier.running-hours windows use Sablier's local time. The image defaults
# to UTC.
services:
sablier:
environment:
TZ: Europe/ParisLearn more about running hours.
Anti-affinitySince v1.15.0
sablier.anti-affinity lists groups the container backs off from: Sablier keeps it idle while any of them has an active session.
services:
streaming:
labels:
- sablier.enable=true
- sablier.group=streaming
image: sablierapp/mimic:v0.3.3
background:
labels:
- sablier.enable=true
- sablier.group=background
- sablier.anti-affinity=streaming # idle while "streaming" is activeLearn more about anti-affinity.
Scale instead of stopSince v1.13.0
Keep the container running when idle, optionally throttled, instead of stopping it:
sablier.idle.replicas=1keeps it running when idle.idle.cpuandidle.memoryset its CPU and memory limits while idle.active.cpuandactive.memoryrestore them on the next session.
services:
whoami-warm:
labels:
- sablier.enable=true
- sablier.group=whoami-warm
- sablier.idle.replicas=1 # keep the container running when idle
- sablier.idle.cpu=0.1 # decimal cores
- sablier.idle.memory=64m # Docker units: b, k, m, g
- sablier.active.cpu=2
- sablier.active.memory=512mSablier changes the limits with the equivalent of docker update --cpus=0.1 --memory=64m.
- CPU is in cores, as a decimal:
0.5is half a core. - Memory uses Docker units:
b,k,morg. - Set the active limits. Docker keeps an idle limit until another update replaces it.
- A memory limit disables swap. Docker needs the swap limit updated with the memory limit, so Sablier sets both to the same value.
- Start the container yourself. In this mode, Sablier never starts or stops it, so use
docker compose upinstead ofcreate. - Set active labels only with an idle replica count of
1or more. Otherwise Sablier stops the container when idle, but only updates its limits on the next session.
Replica counts above 1 and sablier.active.replicas have no effect: a container is one replica.
Learn more about scaling instead of stopping.
Block I/OSince v1.15.0
In scale mode, Sablier can also throttle the container’s block I/O while idle:
services:
backup:
labels:
- sablier.enable=true
- sablier.group=backup
- sablier.idle.replicas=1
- sablier.idle.blkio-weight=100 # relative weight, 10-1000
- sablier.active.blkio-weight=500
# Per-device limits need a daemon with API version 1.55 or newer:
# - sablier.idle.blkio-device-read-bps=/dev/sda:10m
# - sablier.active.blkio-device-read-bps=/dev/sda:100mLabels (idle and active) | Value |
|---|---|
blkio-weight | Relative weight from 10 to 1000. Other values, including 0, are ignored. |
blkio-weight-device | path:weight |
blkio-device-read-bps, blkio-device-write-bps | path:rate, in Docker byte units: 10m is 10 MiB/s. |
blkio-device-read-iops, blkio-device-write-iops | path:iops, an integer. |
- Separate several devices with commas, for example
/dev/sda:10m,/dev/sdb:5m. - Set the active values. Docker keeps an idle limit until another update replaces it.
- Per-device limits need a daemon with API version 1.55 or newer. Older daemons accept the update but ignore them, and Sablier logs a warning.
blkio-weightworks with every version.
If Docker returns warnings for the update, Sablier logs them.
Learn more about throttling block I/O.
Readiness
Sablier reports a container:
- ready when it is running and its health check passes. Without a health check, as soon as it is running.
- starting while it is created, restarting or being removed, or running with a health check that is starting or unhealthy.
- stopped when it exited with code
0, or is paused. - completed when it is a one-shot container that finished.
- error when it exited with another code, or is dead.
Give your containers a health check. Without one, Sablier logs a warning and lets traffic in before the application may be ready. An unhealthy container counts as still starting, so a broken one fails when the request times out.
Limitations
- Sablier manages the containers of one Docker daemon. For Swarm services, use the Docker Swarm provider.
- Sablier doesn’t create containers: create them before the first request.
DOCKER_HOSTdoesn’t supportssh://hosts.- The strategy applies to every container: you can’t pause some and stop others.
- Sablier starts
depends_ondependencies only for containers that Compose created. - A start, dependencies included, times out after five minutes.
- Per-device block I/O limits need a daemon with API version 1.55 or newer.
Examples
examples/docker: every Compose file on this page, with instructions to run them.- Getting started: a complete setup with Docker and Caddy.