Schedule Docker containers on cron — start, stop, or wake on demand.
Stacks, auth, and a dashboard. Single binary, no dependencies.
docker compose up -d
# Open http://localhost:8080 — first run creates an admin account- Cron scheduling — Start and stop containers on any 5-field cron expression (
0 8 * * 1-5= weekdays at 8am) - Stacks — Schedule groups of containers together with shared cron expressions and on-demand wake
- On-demand wake — Wake stopped containers via
/wake/<container>/, auto-redirect when healthy - Inactivity auto-stop — Stop containers after configurable idle timeout (monitors CPU and network activity)
- Authentication — Login, sessions, and three roles (reader, writer, admin). First run prompts for admin setup. OIDC login via Pocket ID or any OpenID Connect provider
- Tags — Reusable schedule templates applied to multiple containers at once
- Log-based restart — Restart a running container when its logs match a rule (substring or regex), with per-rule cooldown and a circuit breaker that auto-disables runaway rules
AGPL-3.0 — a network-copyleft license: if you run a modified version as a network service, you must offer its users the source.
Prerequisites: Docker (runtime), Go 1.25+ (build from source)
docker compose up -dPush to your own registry with make docker-release. Deploying with Portainer? See examples/portainer/.
git clone https://github.com/fabienpiette/schedule-containers.git
cd schedule-containers
make buildschedule-containers serve # Start server + scheduler
schedule-containers schedule add my-app "0 8 * * 1-5" "0 18 * * 1-5" # Add schedule
schedule-containers tag add business-hours --start "0 8 * * 1-5" --stop "0 18 * * 1-5"
schedule-containers tag apply business-hours --containers my-app,redis
schedule-containers log-rule add my-app "OutOfMemoryError" --regex # Restart on log match
schedule-containers schedule export schedules.yaml # Export
schedule-containers schedule import schedules.yaml --dry-run # Import (dry-run)Configure a reverse proxy to redirect to the wake URL when the upstream is down:
app.example.com {
reverse_proxy app:8080
handle_errors {
@is_down expression {http.error.status_code} in [502, 503]
handle @is_down {
redir https://schedule-containers.example.com/wake/app/ permanent
}
}
}When a user accesses the container's URL while it's stopped, they're redirected to the wake page. The container starts, a health check runs, and the user is redirected to the running service.
curl -X POST http://localhost:8080/api/schedules \
-H "Content-Type: application/json" \
-d '{"container_name":"my-app","start_cron":"0 8 * * 1-5","stop_cron":"0 18 * * 1-5","enabled":true}'
curl -X POST http://localhost:8080/api/stacks \
-H "Content-Type: application/json" \
-d '{"name":"my-stack","start_cron":"0 8 * * 1-5","stop_cron":"0 18 * * 1-5","enabled":true}'
curl -X POST http://localhost:8080/api/log-rules \
-H "Content-Type: application/json" \
-d '{"container_name":"my-app","pattern":"OutOfMemoryError","match_type":"regex","enabled":true}'
curl http://localhost:8080/api/containers/my-app/health
curl http://localhost:8080/api/schedules/{id}/wake-urlOpen http://localhost:8080 — view containers, manage schedules and stacks, start/stop containers, create and apply tags, configure on-demand wake, and add log-based restart rules. First run redirects to a setup page to create the admin account.
For all options: schedule-containers --help
| Variable | Default | Description |
|---|---|---|
DB_PATH |
/data/schedule-containers.db |
SQLite database path |
DOCKER_HOST |
unix:///var/run/docker.sock |
Docker socket address |
WEB_PORT |
8080 |
Web server port |
WEB_HOST |
0.0.0.0 |
Web server bind address |
LOG_LEVEL |
info |
Log level: debug, info, warn, error |
TZ |
UTC |
Timezone for cron evaluation |
PRESETS_PATH |
(empty — uses embedded) | Custom presets YAML; if set and file doesn't exist, embedded defaults are copied to it |
OIDC_ISSUER |
(empty) | Pocket ID or OIDC provider URL (no trailing slash) |
OIDC_CLIENT_ID |
(empty) | OIDC client ID |
OIDC_CLIENT_SECRET |
(empty) | OIDC client secret |
OIDC_REDIRECT_URL |
(empty) | OIDC callback URL (e.g. https://yourapp.com/auth/oidc/callback) |
OIDC is disabled unless all four OIDC_* variables are set. When enabled, the login page shows a "Login with Pocket ID" button alongside local password auth.
- Docker socket access — Grants full container control; consider
tecnativa/docker-socket-proxyfor restricted access - CLI doesn't hot-reload —
schedule addwrites directly to SQLite; a running server picks up changes on restart - OIDC auto-provisioning — New OIDC users are created with the
readerrole. Admins can promote them or link them to an existing local account via the admin panel.
- Architecture — codebase orientation for contributors
Contributions welcome — open an issue or pull request. See Architecture for codebase orientation; make build, make test, and make vet cover the common development tasks.
Thanks to all contributors.

