Awesome Go

ofelia

CategoryJob Scheduler
SubcategoryJob Scheduler
Stars68

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

[![Template Drift](https://github.com/netresearch/ofelia/actions/workflows/check-template-drift.yml/badge.svg)](https://github.com/netresearch/ofelia/actions/workflows/check-template-drift.yml)
[![managed by netresearch/.github templates](https://img.shields.io/badge/template-netresearch%2F.github-2F99A4?logo=github)](https://github.com/netresearch/.github/tree/main/templates/go-app)

[![Go Reference](https://pkg.go.dev/badge/github.com/netresearch/ofelia.svg)](https://pkg.go.dev/github.com/netresearch/ofelia)
[![CI](https://github.com/netresearch/ofelia/actions/workflows/ci.yml/badge.svg)](https://github.com/netresearch/ofelia/actions/workflows/ci.yml)
[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json&style=flat&label=benchmarks&logo=codspeed)](https://codspeed.io/netresearch/ofelia)
[![codecov](https://codecov.io/gh/netresearch/ofelia/graph/badge.svg)](https://codecov.io/gh/netresearch/ofelia)
[![CodeQL](https://github.com/netresearch/ofelia/actions/workflows/codeql.yml/badge.svg)](https://github.com/netresearch/ofelia/actions/workflows/codeql.yml)
[![GitHub release](https://img.shields.io/github/v/release/netresearch/ofelia)](https://github.com/netresearch/ofelia/releases/latest)
[![Go Version](https://img.shields.io/github/go-mod/go-version/netresearch/ofelia)](go.mod)
[![License](https://img.shields.io/github/license/netresearch/ofelia)](LICENSE)
[![Container Image](https://img.shields.io/badge/ghcr.io-netresearch%2Fofelia-blue)](https://github.com/netresearch/ofelia/pkgs/container/ofelia)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/11513/badge)](https://www.bestpractices.dev/projects/11513)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/netresearch/ofelia/badge)](https://securityscorecards.dev/viewer/?uri=github.com/netresearch/ofelia)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-3.0-4baaaa.svg)](CODE_OF_CONDUCT.md)
[![SLSA 3](https://slsa.dev/images/gh-badge-level3.svg)](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 Ofelia web UI, light theme on the left and dark on the right](docs/images/dashboard-light-dark.png)

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.

← Back to Job Scheduler