ofelia
Docker job scheduler (crontab for Docker); fork of mcuadros/ofelia that adds a web UI, job dependencies, retries, and job persistence
About ofelia
This large README is shown as a plain-text preview. Read the full README at the source
# Ofelia - a job scheduler
[](https://github.com/netresearch/ofelia/actions/workflows/check-template-drift.yml)
[](https://github.com/netresearch/.github/tree/main/templates/go-app)
[](https://pkg.go.dev/github.com/netresearch/ofelia)
[](https://github.com/netresearch/ofelia/actions/workflows/ci.yml)
[](https://codspeed.io/netresearch/ofelia)
[](https://codecov.io/gh/netresearch/ofelia)
[](https://github.com/netresearch/ofelia/actions/workflows/codeql.yml)
[](https://github.com/netresearch/ofelia/releases/latest)
[](go.mod)
[](LICENSE)
[](https://github.com/netresearch/ofelia/pkgs/container/ofelia)
[](https://www.bestpractices.dev/projects/11513)
[](https://securityscorecards.dev/viewer/?uri=github.com/netresearch/ofelia)
[](CODE_OF_CONDUCT.md)
[](https://slsa.dev)
<img src="https://weirdspace.dk/FranciscoIbanez/Graphics/Ofelia.gif" align="right" width="180px" height="300px" vspace="20" />
**Ofelia** orchestrates container tasks with minimal overhead, offering a sleek alternative to cron.
Label your Docker containers and let this Go-powered daemon handle the schedule.
## Table of Contents
- [Requirements](#requirements)
- [Features](#features)
- [Using it](#using-it)
- [Environment variables](#environment-variables)
- [Configuration precedence](#configuration-precedence)
- [Configuration](#configuration)
- [Development](#development)
- [Documentation](#documentation)
- [Roadmap](#roadmap)
- [License](#license)
## Requirements
### For Docker-based jobs (job-exec, job-run, job-service-run)
**Docker Engine**: Version 17.06 or later (API 1.30+)
- Ofelia requires Docker API 1.30 because it uses the `Env` parameter for exec operations
- The Docker client automatically negotiates the API version with your daemon
- Tested with Docker Engine 29.0+ (API 1.44+)
**Docker socket access**:
- Mount `/var/run/docker.sock` into the Ofelia container (read-only recommended)
- Or set `DOCKER_HOST` environment variable to your Docker daemon
### For Compose jobs (job-compose)
**Docker Compose**: Version 2.0 or later
- Required only if using `job-compose` type
- Must be available in the container's PATH or host system
### For local jobs only (job-local)
No Docker required - runs commands directly on the host system.
### Optional environment variables
- `DOCKER_API_VERSION`: Force a specific Docker API version (not recommended, auto-negotiation works)
- `DOCKER_HOST`: Docker daemon socket/host (default: `unix:///var/run/docker.sock`)
- `DOCKER_CERT_PATH`: Path to Docker TLS certificates
- `DOCKER_TLS_VERIFY`: Enable TLS verification
## Features
- **Job types** for running commands in running containers, new containers,
on the host or as one-off swarm services.
- **Webhook notifications** send job execution results to Slack, Discord, Teams, Matrix, ntfy, Pushover, PagerDuty, Gotify, or *any HTTP endpoint* — set a bundled `preset` for service-specific formatting, or just `url = ...` to POST a JSON payload via the bundled `json-post` fallback. SSRF protection on top.
- **Logging middlewares** integrate with email, file saves, and legacy Slack to report
job output and status.
- **Dynamic Docker detection** uses Docker events by default for instant container
pickup (`--docker-events=true`, the only CLI flag in this group). The other
three knobs are INI-only: `docker-poll-interval` (opt-in container polling for
environments where events are unavailable), `polling-fallback` (default `10s`
— auto-enables polling if the event stream fails), and `config-poll-interval`
(default `10s` — drives INI file reload checks). The legacy `--docker-poll-interval`
CLI flag is deprecated and now sets the unified `Config.Docker.PollInterval`,
which `ApplyDeprecationMigrations` splits into the new INI keys at parse time
— see the [split semantics in Container Detection and Configuration Reloading](#container-detection-and-configuration-reloading).
- **Config validation** via the `validate` command to check your configuration
before running.
- **Optional pprof server** enabled with `--enable-pprof` and bound via
`--pprof-address` for profiling and debugging.
- **Optional web UI** enabled with `--enable-web` and bound via
`--web-address` to view job status. Static files for the UI are embedded in
the binary.
- **Removed job history** keeps deregistered jobs in memory and shows them in the web UI.
- **Enhanced web UI** allows editing and deleting jobs, shows job origin and type, displays each job's configuration and renders the scheduler configuration in a table with empty job sections hidden. Action buttons now use clear icons.
- **Timezone selector** in the web UI lets you view timestamps in local, server or UTC time and remembers your choice.
- **Job dependencies** allow defining execution order with `depends-on`, and conditional triggers with `on-success` and `on-failure` to create job workflows.
> For detailed feature documentation, see [Configuration Reference](docs/CONFIGURATION.md) and [Job Types](docs/jobs.md).
This fork is based off of [mcuadros/ofelia](https://github.com/mcuadros/ofelia).
## Using it
### Docker
The easiest way to deploy **Ofelia** is using a container runtime like **Docker**.
docker pull ghcr.io/netresearch/ofelia
The image exposes a Docker health check so you can use `depends_on.condition: service_healthy` in Docker Compose.
### Standalone
If you don't want to run **Ofelia** using our (Docker) container image, you can download a binary from [our releases page](https://github.com/netresearch/ofelia/releases).
wget https://github.com/netresearch/ofelia/releases/latest
Alternatively, you can build Ofelia from source:
```sh
make packages # build packages under ./bin
# or
go build .
```
### Commands
Use `ofelia daemon` to run the scheduler with a configuration file and
`ofelia validate` to check the configuration without starting the daemon. The
`validate` command prints the complete configuration including applied defaults:
```sh
ofelia daemon --config=/path/to/config.ini
ofelia validate --config=/path/to/config.ini
```
The `--config` flag also supports glob patterns so multiple INI files can be
combined:
```sh
ofelia daemon --config=/etc/ofelia/conf.d/*.ini
```
If `--config` is omitted, Ofelia looks for `/etc/ofelia/config.ini`.
When `--enable-pprof` is specified, the daemon starts a Go pprof HTTP
server for profiling. Use `--pprof-address` to set the listening address
(default `127.0.0.1:8080`).
When `--enable-web` is specified, the daemon serves a small web UI at
`--web-address` (default `:8081`). Besides inspecting running and removed jobs,
the UI allows starting jobs manually, disabling or enabling them and creating,
updating or deleting jobs of all types. A second table lists jobs removed from
the configuration via `/api/jobs/removed`. The endpoint `/api/jobs/{name}/history`
exposes past runs including stdout, stderr and any error messages while
`/api/config` returns the active configuration as JSON. The UI includes a
timezone selector so you can view times in your local zone, the server zone or
UTC and have that preference saved locally.
Creating `run` or `exec` jobs requires Ofelia to run with Docker access; the
server rejects such requests if no Docker client is available.

The UI follows the system color scheme by default; the theme control in the
header switches between auto, light and dark and remembers the choice.
#### Interactive Setup
Use `ofelia init` to create a configuration file interactively:
```sh
ofelia init
```
The wizard guides you through creating jobs step-by-step, prompting for:
- Job type (local, run, exec, service-run)
- Job name and schedule (cron expression)
- Command to execute
- Container name (for Docker-based jobs)
- Optional settings like working directory, user, and environment variables
By default, the configuration is saved to `./ofelia.ini`. Use `--output` (or `-o`)
to specify a different location:
```sh
ofelia init --output=/etc/ofelia/config.ini
```
#### Health Diagnostics
Use `ofelia doctor` to check your Ofelia setup and diagnose common issues:
```sh
ofelia doctor
ofelia doctor --config=/path/to/config.ini
```
The doctor command performs comprehensive health checks:
- **Configuration**: Validates config file syntax and job definitions
- **Docker**: Tests Docker daemon connectivity and permissions
- **Jobs**: Checks for schedule conflicts, invalid cron expressions, and
container references
If no `--config` is specified, doctor searches these locations (in order):
`./ofelia.ini`, `./config.ini`, `/etc/ofelia/config.ini`, `/etc/ofelia.ini`.
Use `--json` for machine-readable output suitable for monitoring integrations.
### Environment variables
You can configure the same options with environment variables. When set,
they override values from the config file and Docker labels.
| Variable | Corresponding flag | Description |
| --- | --- | --- |
| `OFELIA_CONFIG` | `--config` | Path or glob pattern to the configuration file(s) |
| `OFELIA_DOCKER_FILTER` | `--docker-filter` | Docker container filter (comma separated for multiple) |
| `OFELIA_POLL_INTERVAL` | `--docker-poll-interval` | **Deprecated** — legacy unified poll interval. The CLI flag stays for backward compatibility; prefer setting `config-poll-interval` (INI reloads) and `docker-poll-interval` (container polling, opt-in) directly in the INI `[global]` section. See [Container Detection and Configuration Reloading](#container-detection-and-configuration-reloading). |
| `OFELIA_DOCKER_EVENTS` | `--docker-events` | Use Docker events for container detection (default `true`; disable for opt-in polling) |
| `OFELIA_DOCKER_NO_POLL` | `--docker-no-poll` | Disable polling Docker for labels |
| `OFELIA_DOCKER_INCLUDE_STOPPED` | `--docker-include-stopped` | Include stopped containers when reading Docker labels (only for job-run) |
| `OFELIA_LOG_LEVEL` | `--log-level` | Set the log level |
| `OFELIA_ENABLE_PPROF` | `--enable-pprof` | Enable the pprof HTTP server |
| `OFELIA_PPROF_ADDRESS` | `--pprof-address` | Address for the pprof server |
| `OFELIA_ENABLE_WEB` | `--enable-web` | Enable the web UI |
| `OFELIA_WEB_ADDRESS` | `--web-address` | Address for the web UI server |
> See [Configuration Reference](docs/CONFIGURATION.md) for all available options including job-specific parameters.
### Configuration precedence
Ofelia merges options from multiple sources in the following order. Values from later sources override earlier ones:
1. Built-in defaults
2. `config.ini`
3. Docker labels
4. Command-line flags
5. Environment variables
The daemon watches `config.ini` and reloads it automatically when the file changes.
Job definitions and most `[global]` middleware options (`slack-*`, `save-*`,
`mail-*`, `log-level`, `max-runtime`) are applied on reload. Options that start
servers (`enable-web`, `web-address`, `enable-pprof`, `pprof-address`) and all
`[docker]` settings require restarting the daemon.
## Configuration
### Jobs
#### Scheduling format
This application uses the [Go implementation of `cron`](https://pkg.go.dev/github.com/robfig/cron) with a parser for supporting optional seconds.
Supported formats:
- `@every 10s`
- `20 0 1 * * *` (every night, 20 seconds after 1 AM - [Quartz format](http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html)
- `0 1 * * *` (every night at 1 AM - standard [cron format](https://en.wikipedia.org/wiki/Cron)).
You can configure four different kinds of jobs:
- `job-exec`: this job is executed inside of a running container.
- `job-run`: runs a command inside of a new container, using a specific image.
- `job-local`: runs the command inside of the host running ofelia.
- `job-service-run`: runs the command inside a new "run-once" service, for running inside a swarm
- `job-compose`: runs a command using `docker compose run` or `docker compose exec` based on a compose file
See [Jobs reference documentation](docs/jobs.md) for all available parameters.
See [Architecture overview](docs/architecture.md) for details about the scheduler, job types and middleware.
### Logging
**Ofelia** comes with several logging/notification drivers:
- `webhook` to send notifications via Slack, Discord, Teams, ntfy, Pushover, PagerDuty, Gotify, or custom HTTP endpoints. See [Webhook Documentation](docs/webhooks.md) for configuration details.
- `mail` to send mails
- `save` to save structured execution reports to a directory. The destination folder is created automatically if it doesn't exist.
- `slack` (**deprecated**) to send messages via a slack webhook - migrate to the new webhook system
### Global Options
- `smtp-host` - address of the SMTP server.
- `smtp-port` - port number of the SMTP server.
- `smtp-user` - user name used to connect to the SMTP server.
- `smtp-password` - password used to connect to the SMTP server.
- `smtp-tls-skip-verify` - when `true` ignores certificate signed by unknown authority error.
- `email-to` - mail address of the receiver of the mail.
- `email-from` - mail address of the sender of the mail.
- `email-subject` - custom subject template for emails. Uses Go template syntax with access to `.Job` and `.Execution`. Example: `[ALERT] Job {{.Job.GetName}} {{status .Execution}}`. If not set, uses default format.
- `mail-only-on-error` - only send a mail if the execution was not successful.
- `save-folder` - directory in which the reports shall be written. The folder is created automatically if it doesn't exist using an equivalent of `mkdir -p`.
- `save-only-on-error` - only save a report if the execution was not successful.
- `save-mode` - octal file mode for the per-execution log/JSON files (default `0600`, readable only by the daemon uid because job output may contain secrets/PII). Set a wider value such as `0644` to let non-root operators or a shared group read the logs on the host. Accepts `0644`, `0o644` or `644`. Like `save-folder`, this is **not** in the global Docker-label allow-list, so a container cannot change the daemon-wide default.
- `save-folder-mode` - octal directory mode for `save-folder` (default `0750`). Accepts the same formats as `save-mode`. Applies only when Ofelia creates the folder; an existing `save-folder` keeps its current permissions (`mkdir -p` semantics).
- `webhook-webhooks` - comma-separated list of webhook names to apply to every job by default.
- `webhook-default-preset` - preset name used when a webhook configuration omits `preset` (default: `json-post`, the bundled JSON POST preset that lets `url = ...` work without authoring a custom preset). Set to empty (`webhook-default-preset =`) to opt out — webhooks must then declare `preset` explicitly or attachment fails. See [Webhook Documentation](docs/webhooks.md#custom-webhooks-url-only-no-custom-preset).
- `webhook-allow-remote-presets` - allow fetching presets from remote URLs (default: `false`).
- `webhook-trusted-preset-sources` - comma-separated allow-list of remote preset sources (supports glob, e.g. `gh:netresearch/*`).
- `webhook-preset-cache-ttl` - cache duration for remote presets (default: `24h`).
- `webhook-preset-cache-dir` - directory used to cache remote presets.
- `webhook-allowed-hosts` - comma-separated allow-list of webhook target hosts (default: `*`; supports wildcards).
- `slack-webhook` - (**deprecated**) URL of the slack webhook. Migrate to `[webhook "name"]` sections.
- `slack-only-on-error` - (**deprecated**) only send a slack message if the execution was not successful.
- `log-level` - logging level (DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL). When set in the config file this level is applied from startup unless `--log-level` is provided.
- `enable-web` - enable the built-in web UI.
- `web-address` - address for the web UI server (default `:8081`).
- `enable-pprof` - enable the pprof debug server.
- `pprof-address` - address for the pprof server (default `127.0.0.1:8080`).
- `max-runtime` - default maximum duration a run or service job may run (default `24h`).
Log output now includes the original file and line of the logging call instead of the adapter location.
### INI-style configuration
Run with `ofelia daemon --config=/path/to/config.ini` or use a glob pattern like
`/etc/ofelia/conf.d/*.ini` to load multiple files
```ini
[global]
save-folder = /var/log/ofelia_reports
save-only-on-error = true
log-level = INFO
enable-web = true
web-address = :8081
enable-pprof = true
pprof-address = 127.0.0.1:8080
[job-exec "job-executed-on-running-container"]
schedule = @hourly
container = my-container
command = touch /tmp/example
[job-run "job-executed-on-new-container"]
schedule = @hourly
image = ubuntu:latest
command = touch /tmp/example
[job-local "job-executed-on-current-host"]
schedule = @hourly
command = touch /tmp/example
[job-service-run "service-executed-on-new-container"]
schedule = 0,20,40 * * * *
image = ubuntu
network = swarm_network
command = touch /tmp/example
```
### Docker label configurations
In order to use this type of configuration, Ofelia needs access to the Docker socket.
> ⚠ **Warning**: This command changed! Please remove the `--docker` flag from your command.
```sh
docker run -it --rm \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label ofelia.save-folder="/var/log/ofelia_reports" \
--label ofelia.save-only-on-error="true" \
--label ofelia.log-level="INFO" \
--label ofelia.enable-web="true" \
--label ofelia.web-address=":8081" \
--label ofelia.enable-pprof="true" \
--label ofelia.pprof-address="127.0.0.1:8080" \
ghcr.io/netresearch/ofelia:latest daemon
```
Labels format: `ofelia.<JOB_TYPE>.<JOB_NAME>.<JOB_PARAMETER>=<PARAMETER_VALUE>`.
This type of configuration supports all the capabilities provided by INI files, including the global logging options.
For `job-exec` labels, Ofelia automatically prefixes the container name to the job name to avoid collisions. A label `ofelia.job-exec.optimize` on a container named `gitlab` will result in a job called `gitlab.optimize`.
Also, it is possible to configure `job-exec` by setting labels configurations on the target container. To do that, additional label `ofelia.enabled=true` need to be present on the target container.
For example, we want `ofelia` to execute `uname -a` command in the existing container called `nginx`.
To do that, we need to start the `nginx` container with the following configurations:
```sh
docker run -it --rm \
--label ofelia.enabled=true \
--label ofelia.job-exec.test-exec-job.schedule="@every 5s" \Frequently Asked Questions
What is ofelia?
ofelia is a Job Scheduler library for the Go programming language. Docker job scheduler (crontab for Docker); fork of mcuadros/ofelia that adds a web UI, job dependencies, retries, and job persistence
How do I install ofelia?
Install ofelia with the Go module system using `go get netresearch/ofelia`. Check the repository for the current installation instructions.
What category does ofelia belong to?
ofelia is listed under Job Scheduler, specifically Job Scheduler.