# Ofelia - a job scheduler [![GitHub version](https://img.shields.io/github/v/release/mcuadros/ofelia?&sort=semver&display_name=release&label=stable&color=green)](https://github.com/mcuadros/ofelia/releases) [![GitHub version](https://img.shields.io/github/v/release/mcuadros/ofelia?include_prereleases&sort=semver&display_name=release&label=edge)](https://github.com/mcuadros/ofelia/releases) ![Test](https://github.com/mcuadros/ofelia/workflows/Test/badge.svg) **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)