Skip to content

Job Scheduling

The built-in job scheduler allows you to run containers/services defined in your docker compose files as scheduled jobs based on cron-like schedules or predefined intervals. This is useful for running periodic tasks such as backups, maintenance scripts, or any recurring workloads without needing an external scheduler.

Multiple doco-cd instances on the same Docker host

The scheduler discovers runnable jobs from Docker labels and is not scoped by deployment target or by a specific .doco-cd.*.yaml file. If you run multiple doco-cd instances against the same Docker socket, each instance can discover and trigger the same scheduled jobs.

To avoid duplicate runs, enable the scheduler only on the instance that should own scheduled jobs and set SCHEDULER_ENABLED to false on secondary or self-updater instances.

Schedule formats

Tip

Use an online cron expression generator like crontab.guru to create and validate cron expressions.

Schedule examples

docker-compose.yml
services:
  backup:
    image: example/backup:latest
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "*/15 * * * *"
docker-compose.yml
services:
  backup:
    image: example/backup:latest
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "@every 15m"
docker-compose.yml
services:
  backup:
    image: example/backup:latest
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "30 2 * * 1-5"
docker-compose.yml
services:
  cleanup:
    image: example/backup:latest
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "0 0 1 * *"
docker-compose.yml
services:
  cleanup:
    image: example/backup:latest
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "@monthly"

Execution modes

The execution mode determines how scheduled jobs are run and managed by doco-cd and can be configured using the cd.doco.job.execution_mode label on the service.

Scheduled jobs never run on deployment

Scheduled jobs only run when their schedule fires, never as a side effect of a (re)deployment. On deployment the job's service/container is prepared but left idle:

  • Docker (Standalone): the container is created but not started.
  • Docker Swarm: the service is deployed with 0 replicas (see the limitation for global restart-mode jobs below).

restart

By default, scheduled jobs will be executed in restart mode, which means the service will be created on deployment and then re-/started at the scheduled time without being removed after completion.

Docker (Standalone) Compose services

Scheduled services are started with their Compose service definition, so Compose-defined secrets and configs are applied when the job starts. Restart-mode services must have an effective scale of 1; use one_off for a multi-replica workload.

Docker Swarm global + restart limitation

In Docker Swarm, restart-mode scheduled jobs are deployed with 0 replicas so they do not run on deployment. global services cannot be scaled to 0 (a global service always runs one task per node), so a global service combined with restart mode will run on deployment. Use one_off mode for global scheduled jobs instead.

one_off

Alternatively, you can configure scheduled jobs to run in one_off mode, which means a new ephemeral container will be created for each scheduled run and removed after completion.

Note

You won't be able to see the container or its logs after the job has completed, so make sure to configure appropriate logging (e.g., log to a persistent file or logging service like Loki) if you need to keep track of job runs and notifications if needed.

one_off behavior in Docker Swarm

In Docker Swarm, one_off does not modify the source service mode permanently. Instead, doco-cd creates a temporary job service for each scheduled run, waits for completion, and removes that temporary service afterwards.

This means the original service may still show replicated/global when inspected, while each one-off execution runs as a temporary replicated-job/global-job service.

See also Swarm deploy.mode configuration for how the original service's deploy mode affects the temporary job service's deploy mode in one-off executions.

Behavior summary

cd.doco.job.execution_mode What doco-cd acts on Service mode after run
restart Existing service Unchanged
one_off Temporary clone Source unchanged
Deprecated: one_shot has been renamed to one_off

one_shot has been renamed to one_off and will be removed in a future release. Use one_off instead. The old value is still accepted for backward compatibility but will log a warning.

Configuration

How to set service labels in a docker compose file

To set service labels in a docker compose file, include them in the labels section of your service definition:

docker-compose.yml
services:
  app:
    image: ghcr.io/example/app:latest
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "@every 15m"

Restart policy constraints

  • Docker (Standalone): service restart must be unset or no
  • Docker Swarm: service deploy.restart_policy.condition must be unset or none

Use the following service labels to configure scheduled jobs:

Label Type Description Default
cd.doco.job.enabled boolean Enable scheduling for this service/container false
cd.doco.job.schedule string Schedule format to use
cd.doco.job.wait_running_jobs boolean Override deploy-config-wide wait_running_jobs behavior for this job service during deployments (inherit)
cd.doco.job.execution_mode string restart (default behavior) or one_off (ephemeral execution) restart
cd.doco.job.skip_running boolean Do not run the job if a previous scheduled run is still active/running false
cd.doco.job.notify_on string Notification behavior for scheduled runs: none, success, failure, all all
cd.doco.job.swarm.replicas integer Number of completions/concurrency for swarm one-off jobs in replicated deploy mode 1
cd.doco.job.stop_services string Comma-separated services to temporarily stop during a job run (supports service and project/service; Swarm requires execution_mode: one_off)

Using scheduled jobs with multiple doco-cd instances

cd.doco.job.skip_running only prevents overlapping runs within the same doco-cd process. It does not coordinate scheduled runs across multiple doco-cd instances that share the same Docker host.

For multi-instance setups, prefer a single scheduler owner by disabling the scheduler on the other instances with SCHEDULER_ENABLED.

Swarm deploy.mode

When using Docker Swarm, you can configure the deploy mode for scheduled jobs using the deploy.mode field in your docker compose file.

The following mapping applies to scheduled runs in one_off mode:

  • If the service uses deploy.mode: global, the job run is created as global-job
  • If the service uses deploy.mode: replicated or does not specify a deploy mode, the job run is created as replicated-job with the number of completions/concurrency determined by the cd.doco.job.swarm.replicas label.

Temporarily stop services during a job run

Use cd.doco.job.stop_services when a scheduled job needs a quiet window (for example, cold backups):

docker-compose.yml
services:
  backup:
    image: ghcr.io/my-org/backup:1.2.3
    command: ["/backup.sh"]
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "0 2 * * *"
      cd.doco.job.execution_mode: "one_off"
      cd.doco.job.stop_services: "app,other-project/other-app"

Behavior:

  • Before the job starts, listed services are stopped, and doco-cd waits until their containers/tasks have actually terminated.
  • After the job finishes (success or failure), listed services are started again.
  • service targets the same project/stack as the job.
  • project/service targets another compose project (standalone) or stack (swarm).

Execution mode support

cd.doco.job.stop_services is supported in both execution modes for standalone compose:

Mode Standalone Swarm
one_off
restart (default)

In Swarm mode, restart is not supported because doco-cd cannot detect when the job has finished.

Use service names, not container names

Values must reference the compose service name (the key under services:), not container_name.

depends_on is not traversed automatically

Only explicitly listed services are stopped/started. If dependent services should also be paused, include them explicitly in cd.doco.job.stop_services.

Swarm: global-mode services are skipped

Services deployed with deploy.mode: global (or global-job) cannot be scaled to 0 replicas, so they are skipped with a warning instead of being stopped.

Concurrency and shared targets

While services are held stopped, doco-cd locks the job's own stack and every stack referenced by cd.doco.job.stop_services, so a concurrent deployment or another scheduled run cannot race with the reconciliation of those stacks.

If two scheduled jobs happen to list the same target service (e.g. two backup jobs sharing a cache), the target is only actually restarted once every job that stopped it has finished — it will not be brought back up prematurely while another job still needs it stopped.

Examples

Prune Docker system every hour on all swarm nodes using a global one-off job service

docker-compose.yml
services:
  prune:
    image: docker:latest
    command: ["docker", "system", "prune", "-f"]
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      mode: global
      restart_policy:
        condition: none
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "@hourly"
      cd.doco.job.execution_mode: "one_off"

Run a backup script every day at 02:00, but skip if the previous run is still active

docker-compose.yml
services:
  backup:
    image: ghcr.io/my-org/backup:1.2.3
    command: ["/backup.sh"]
    restart: no
    labels:
      cd.doco.job.enabled: "true"
      cd.doco.job.schedule: "0 2 * * *"
      cd.doco.job.skip_running: "true"

Timezone

Scheduled jobs are triggered based on the timezone of the doco-cd instance, which is determined by the TZ environment variable or defaults to UTC if not set. You can find a list of all possible timezone values on timeie and wikipedia.

Daylight saving time (DST)

When DST changes occur in the configured timezone, scheduled jobs will adjust accordingly:

  • If a scheduled time is skipped due to DST (e.g., clocks move forward), the job will not run at that time.
  • If a scheduled time occurs twice due to DST (e.g., clocks move backward), the job will run at both occurrences of that time.

Manual execution via Job API

Configured jobs can also be triggered manually outside their scheduled intervals by using the Run Job API endpoint.