A lightweight, Alpine-based Docker image designed to run and manage long-running processes using Supervisor, with built-in support for:
docker execwrappers (pexe&dexe) with TTY auto-detection- Scheduled log rotation via
logrotate(worker loop + persisted state) - Cron support via
cronie(logs to Docker stdout) - Clean log management for scalable environments
- Built-in container HEALTHCHECK (Supervisor responsiveness)
| Registry | Image Name |
|---|---|
| Docker Hub | docker.io/infocyph/runner |
| GitHub Container | ghcr.io/infocyph/runner |
The container starts Supervisor as PID 1 which manages:
crond(foreground, logs to Docker stdout)- a
logrotateworker loop that runs periodically
You can mount additional Supervisor programs via /etc/supervisor/conf.d.
The image includes a healthcheck that verifies Supervisor is responsive:
- Interval: 30s
- Timeout: 3s
- Start period: 10s
- Retries: 3
If Supervisor becomes unresponsive, the container will be reported as unhealthy.
Run any command inside a container (TTY auto-detected):
dexe <container_name> <command> [...args]Example:
dexe my-app echo "Hello from inside"Run PHP inside a container (TTY auto-detected):
pexe <container_name> <php_args...>Example:
pexe my-php-app artisan migrateNote:
pexeis a thin wrapper overdocker exec ... php ...
LOGROTATE_INTERVAL(seconds) — default:3600LOGROTATE_STATE_FILE— default:/var/lib/logrotate/statusTZ— optional timezone for consistent scheduling/log timestamps
Example:
-e TZ=Asia/Dhaka \
-e LOGROTATE_INTERVAL=3600- Logrotate configs are loaded from:
/etc/logrotate.d/ - The worker prefers
/etc/logrotate.confif present; otherwise it rotates each file in/etc/logrotate.d/* - Log files are rotated daily by default (per configs)
- Rotation status is tracked in
LOGROTATE_STATE_FILEto avoid repeated rotations across restarts
Mount any directory into /global/log/* and any *.log inside (including subdirs) will rotate daily.
-v $(pwd)/logs:/global/log/my-appMount logs that you want rotated daily and moved into /global/oldlogs.
-v $(pwd)/movelogs:/global/movelog/my-app
-v $(pwd)/oldlogs:/global/oldlogsSupervisor’s own logs (and any program logs you write there) rotate daily.
-v $(pwd)/logs/runner:/var/log/supervisorYour logrotate configs emit a post-rotate line into Docker logs (stdout of PID 1), e.g.:
[logrotate] rotated /global/log (daily) at ...[logrotate] rotated /global/movelog -> /global/oldlogs (daily) at ...[logrotate] rotated /var/log/supervisor (daily) at ...
This is done by writing to /proc/1/fd/1 to reliably land in docker logs.
Cron runs in the foreground with logging enabled:
crond -f -l 2 -L /dev/stdout
To add cron jobs, mount /etc/cron.d:
-v ./cron-jobs:/etc/cron.d:roExample file: my-cron:
* * * * * root echo "Cron ran at $(date)" >> /global/log/my-cron.log 2>&1docker run -d \
--name runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ./supervisor:/etc/supervisor/conf.d:ro \
-v ./cron-jobs:/etc/cron.d:ro \
-v ./logs/runner:/var/log/supervisor \
-v $(pwd)/logs:/global/log \
-v $(pwd)/movelogs:/global/movelog \
-v $(pwd)/oldlogs:/global/oldlogs \
infocyph/runnerMount /etc/supervisor/conf.d to add Supervisor programs.
Example: my-task.conf
[program:my-task]
command=/bin/sh -c 'echo Hello World && sleep 60'
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/my-task.err.log
stdout_logfile=/var/log/supervisor/my-task.out.log
[program:scheduler]
command=pexe MY_PHP_CONTAINER artisan schedule:run
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/scheduler.err.log
stdout_logfile=/var/log/supervisor/scheduler.out.logTip: If you want scheduler to run periodically, prefer cron calling
pexe ... schedule:run.
docker ps --format "table {{.Names}}\t{{.Status}}"
docker inspect --format '{{json .State.Health}}' runner | jq .If the container is unhealthy, it means supervisorctl status failed (Supervisor not responding).
docker exec -it runner supervisorctl -c /etc/supervisor/supervisord.conf status-
Docker logs (recommended):
docker logs -f runner
You should see cron logs and logrotate worker logs here.
-
Supervisor log file (if you mounted it):
/var/log/supervisor/supervisord.log
Common causes:
-
The file in
/etc/cron.dhas wrong permissions/format. Keep it simple:- one job per line
- includes the user field (e.g.
root)
-
Ensure your cron job writes somewhere writable (example uses
/global/log/...).
Quick validation:
docker exec -it runner ls -la /etc/cron.d
docker logs -f runner | grep -i cronThings to check:
-
Confirm the worker is running:
docker exec -it runner supervisorctl -c /etc/supervisor/supervisord.conf status -
Confirm your logs match the patterns:
/global/log/**/*.log/global/movelog/**/*.log/var/log/supervisor/*.log
-
Confirm state file exists (rotation is stateful):
docker exec -it runner ls -la /var/lib/logrotate/status -
Force a single manual run (for testing only):
docker exec -it runner /usr/sbin/logrotate -v -s /var/lib/logrotate/status /etc/logrotate.d/daily
These two issues cause 80% of “cron/logrotate not working” reports:
- CRLF in mounted files (especially from Windows)
-
Symptoms: “bad minute”, “^M”, jobs ignored, scripts not executed.
-
Fix on host:
sed -i 's/\r$//' ./cron-jobs/* ./supervisor/*.conf 2>/dev/null || true
- Wrong permissions / ownership
/etc/cron.d/*should be readable by root.- Your log dirs should be writable by the processes writing logs.
Quick checks:
docker exec -it runner sh -lc 'ls -la /etc/cron.d /etc/supervisor/conf.d /global/log /global/movelog /global/oldlogs /var/log/supervisor'Most common mistake: mounting the wrong path.
Correct examples:
-v $(pwd)/logs:/global/log/my-app
-v $(pwd)/movelogs:/global/movelog/my-app
-v $(pwd)/oldlogs:/global/oldlogsYour configs write to /proc/1/fd/1. If you run the container without Supervisor as PID 1 (custom entrypoint), this won’t work.
Make sure you’re using the default CMD:
supervisord -c /etc/supervisor/supervisord.conf- Mounting
/var/run/docker.sockallowspexeanddexeto exec into other containers. /etc/supervisor/conf.dand/etc/cron.dshould typically be mounted read-only (:ro).
MIT © infocyph