# Ofelia - a job scheduler [](https://github.com/mcuadros/ofelia/releases) [](https://github.com/mcuadros/ofelia/releases) 
**Ofelia** is a modern and low footprint job scheduler for __docker__ environments, built on Go. Ofelia aims to be a replacement for the old fashioned [cron](https://en.wikipedia.org/wiki/Cron).
### Why?
It has been a long time since [`cron`](https://en.wikipedia.org/wiki/Cron) was released, actually more than 28 years. The world has changed a lot and especially since the `Docker` revolution. **Vixie's cron** works great but it's not extensible and it's hard to debug when something goes wrong.
Many solutions are available: ready to go containerized `crons`, wrappers for your commands, etc. but in the end simple tasks become complex.
### How?
The main feature of **Ofelia** is the ability to execute commands directly on Docker containers. Using Docker's API Ofelia emulates the behavior of [`exec`](https://docs.docker.com/reference/cli/docker/container/exec/), being able to run a command inside of a running container. Also you can run the command in a new container destroying it at the end of the execution.
## Configuration
### Jobs
[Scheduling format](https://pkg.go.dev/github.com/robfig/cron/v3#hdr-CRON_Expression_Format) is the same as the Go implementation of `cron`. E.g. `@every 10s` or `0 1 * * *` (every night at 1 AM).
>[!NOTE]
>[robfig/cron/v1](https://pkg.go.dev/github.com/robfig/cron@v1.2.0#hdr-CRON_Expression_Format) accepted an seconds field at the beginning of the cron spec. Starting with `0.4.x`, **Ofelia** still supports this format for backward compatibility, using seconds is optional and not recommended for new configurations. However, for `0.3.x` (`latest`) seconds configuration is still **required**.
You can configure four different kind 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
See [Jobs reference documentation](docs/jobs.md) for all available parameters.
#### INI-style config
Run with `ofelia daemon --config=/path/to/config.ini`
```ini
[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 labels configurations
In order to use this type of configurations, ofelia need access to docker socket.
```sh
docker run -it --rm \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label ofelia.job-local.my-test-job.schedule="@every 5s" \
--label ofelia.job-local.my-test-job.command="date" \
mcuadros/ofelia:latest daemon --docker
```
Labels format: `ofelia...=`.
This type of configuration supports all the capabilities provided by INI files.
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 `my_nginx`.
To do that, we need to we need to start `my_nginx` container with next configurations:
```sh
docker run -it --rm \
--label ofelia.enabled=true \
--label ofelia.job-exec.test-exec-job.schedule="@every 5s" \
--label ofelia.job-exec.test-exec-job.command="uname -a" \
nginx
```
Now if we start `ofelia` container with the command provided above, it will pickup 2 jobs:
- Local - `date`
- Exec - `uname -a`
Or with docker-compose:
```yaml
version: "3"
services:
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- nginx
command: daemon --docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
ofelia.job-local.my-test-job.schedule: "@every 5s"
ofelia.job-local.my-test-job.command: "date"
nginx:
image: nginx
labels:
ofelia.enabled: "true"
ofelia.job-exec.datecron.schedule: "@every 5s"
ofelia.job-exec.datecron.command: "uname -a"
```
>[!NOTE]
>For more advanced docker-compose usage example see [docker-compose.yml](./integration/test-run-exec/docker-compose.yml) used in integration tests.
**Ofelia** reads labels of all Docker containers for configuration by default. To apply on a subset of containers only, use the flag `--docker-filter` (or `-f`) similar to the [filtering for `docker ps`](https://docs.docker.com/engine/reference/commandline/ps/#filter). E.g. to apply to current docker compose project only using `label` filter:
```yaml
version: "3"
services:
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- nginx
command: daemon --docker -f label=com.docker.compose.project=${COMPOSE_PROJECT_NAME}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
ofelia.job-local.my-test-job.schedule: "@every 5s"
ofelia.job-local.my-test-job.command: "date"
nginx:
image: nginx
labels:
ofelia.enabled: "true"
ofelia.job-exec.datecron.schedule: "@every 5s"
ofelia.job-exec.datecron.command: "uname -a"
```
#### Docker host configuration
By default, **Ofelia** connects to the Docker daemon via the default socket (`/var/run/docker.sock` on Linux). However, you can configure it to connect to a different Docker host using environment variables. This is particularly useful when:
- Using a Docker socket proxy for security
- Connecting to a remote Docker daemon
- Using Docker over TCP
**Ofelia** supports the following Docker environment variables:
- `DOCKER_HOST` - The Docker host to connect to (e.g., `tcp://docker-proxy:2375`, `unix:///custom/docker.sock`)
- `DOCKER_TLS_VERIFY` - Enable TLS verification (set to `1` to enable)
- `DOCKER_CERT_PATH` - Path to TLS certificates directory
- `DOCKER_API_VERSION` - Docker API version to use
##### Using with a socket proxy
```sh
docker run -it --rm \
-e DOCKER_HOST=tcp://docker-proxy:2375 \
--label ofelia.job-local.my-test-job.schedule="@every 5s" \
--label ofelia.job-local.my-test-job.command="date" \
mcuadros/ofelia:latest daemon --docker
```
Or with docker-compose:
```yaml
version: "3"
services:
docker-proxy:
image: tecnativa/docker-socket-proxy
environment:
CONTAINERS: 1
SERVICES: 1
TASKS: 1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
ofelia:
image: mcuadros/ofelia:latest
depends_on:
- docker-proxy
- nginx
command: daemon --docker
environment:
DOCKER_HOST: tcp://docker-proxy:2375
labels:
ofelia.job-local.my-test-job.schedule: "@every 5s"
ofelia.job-local.my-test-job.command: "date"
nginx:
image: nginx
labels:
ofelia.enabled: "true"
ofelia.job-exec.datecron.schedule: "@every 5s"
ofelia.job-exec.datecron.command: "uname -a"
```
##### Using with TLS
```sh
docker run -it --rm \
-e DOCKER_HOST=tcp://docker.example.com:2376 \
-e DOCKER_TLS_VERIFY=1 \
-e DOCKER_CERT_PATH=/certs \
-v /path/to/certs:/certs:ro \
mcuadros/ofelia:latest daemon --config=/path/to/config.ini
```
### Logging
**Ofelia** comes with three different logging drivers:
- `mail` to send mails
- `save` to save structured execution reports to a directory
- `slack` to send messages via a slack webhook
These can be configured by setting the options listed below in the `[global]` section of your config.ini, or via docker labels on the `ofelia` container (regardless of where your job will actually be running).
#### 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.
- `mail-only-on-error` - only send a mail if the execution was not successful.
- `save-folder` - directory in which the reports shall be written (must already exist).
- `save-only-on-error` - only save a report if the execution was not successful.
- `slack-webhook` - URL of the slack webhook.
- `slack-only-on-error` - only send a slack message if the execution was not successful.
### Overlap
**Ofelia** can prevent that a job is run twice in parallel (e.g. if the first execution didn't complete before a second execution was scheduled. If a job has the option `no-overlap` set, it will not be run concurrently.
## Installation
The easiest way to deploy **ofelia** is using *Docker*. See examples above.
If don't want to run **ofelia** using our *Docker* image you can download a binary from [releases](https://github.com/mcuadros/ofelia/releases) page.
> Why the project is named Ofelia? Ofelia is the name of the office assistant from the Spanish comic [Mortadelo y Filemón](https://en.wikipedia.org/wiki/Mort_%26_Phil)