Skip to content

Running in Docker

The official image is runwisp/runwisp, for amd64 and arm64. It contains the same release binary as the install script and the npm package.

compose.yaml
services:
runwisp:
image: runwisp/runwisp:latest
restart: unless-stopped
ports:
- "9477:9477"
environment:
- RUNWISP_PASSWORD=change-me
volumes:
- ./runwisp.toml:/etc/runwisp/runwisp.toml:ro
- runwisp-data:/var/lib/runwisp
volumes:
runwisp-data:

Run docker compose up -d, open http://localhost:9477, and log in with the password. The same with docker run:

Terminal window
docker run -d --name runwisp \
-p 9477:9477 \
-e RUNWISP_PASSWORD=change-me \
-v ./runwisp.toml:/etc/runwisp/runwisp.toml:ro \
-v runwisp-data:/var/lib/runwisp \
runwisp/runwisp:latest
Base Tags Notes
Alpine (default) latest, X.Y.Z, X.Y, X, and the same four with -alpine Smaller; busybox and apk
Debian slim latest-debian, X.Y.Z-debian, X.Y-debian, X-debian glibc and apt-get
Alpine + Docker latest-docker, and -alpine-docker / X.Y.Z / X.Y / X variants Adds a Docker CLI + Compose plugin, ~100MB more
Debian + Docker latest-debian-docker, X.Y.Z-debian-docker, X.Y-debian-docker, X-debian-docker Same, on the Debian base
  • Pick Debian if your tasks need glibc or a Debian-only package, otherwise Alpine.
  • Pick a -docker tag only if your tasks run docker themselves (see Docker tasks).
  • X.Y.Z is the only tag that never changes. X.Y, X, and latest move with new releases. Breaking changes only ship in a new major version, so 1 is safe to follow.
  • A prerelease gets only its exact X.Y.Z tag, so latest never points at a release candidate.

The entrypoint refuses to start the daemon unless both of these are set, and exits 1 with a message saying what’s missing.

An auth setting. Set either RUNWISP_PASSWORD or RUNWISP_AUTH=off (no login, trusted networks only). Without one, the daemon would generate a new random password on every start, which nobody could read from a container. See Authentication.

A config file mounted at /etc/runwisp/runwisp.toml. Read-only is fine; RunWisp never writes it. There is no scaffold prompt in a container.

Both checks apply to anything that starts a daemon: daemon, station, restart, demo, bare runwisp, and any subcommand the entrypoint doesn’t know. One-shot commands such as validate, run, list, status, reload, import, and tui skip them:

Terminal window
docker run --rm -v ./runwisp.toml:/etc/runwisp/runwisp.toml:ro \
runwisp/runwisp:latest runwisp validate

The image sets these. Override any of them with -e.

Variable Image default Purpose
RUNWISP_CONFIG /etc/runwisp/runwisp.toml Where the config is read from.
RUNWISP_DATA /var/lib/runwisp SQLite database, run logs, and the control socket.
RUNWISP_HOST 0.0.0.0 Listen on all interfaces, so port mapping works.
RUNWISP_TLS unset (daemon defaults to off) See Plain HTTP by default.
RUNWISP_LOG_FORMAT text Readable docker logs. Set json if a log collector parses them.

Use -e RUNWISP_DATA=… rather than the --data flag. The healthcheck reads only the environment, so a flag makes it look in the wrong place. The entrypoint warns if you pass --data or --socket.

  • /etc/runwisp/runwisp.toml: your config, read-only. It must be a file. To split config across files, mount a directory at /etc/runwisp with runwisp.toml inside, and load the rest with include.
  • /var/lib/runwisp: the database and run logs. Without a volume here, every container recreate deletes your run history and logs you out.

When a task runs, RunWisp runs its run command with /bin/sh -c inside the RunWisp container. So every tool your tasks call must be in the image. The image has bash, tzdata, and ca-certificates, and nothing else: no curl, no git, no database clients, no Docker CLI. Add what you need with a Dockerfile, or run the task in another container. See Docker tasks.

The image has a HEALTHCHECK that runs runwisp status, so docker ps and compose’s depends_on: condition: service_healthy work without setup.

It talks to the control socket, which it finds from RUNWISP_DATA or RUNWISP_SOCKET. A --data flag on the command line isn’t visible to it, and the container then reports unhealthy although the daemon is fine. Use the environment variables, or --no-healthcheck to supply your own.

The daemon serves plain HTTP. Either:

  • put a reverse proxy in front for TLS and set RUNWISP_TRUSTED_PROXIES to its CIDR, so session cookies are still marked Secure (trusted_proxies); or
  • set -e RUNWISP_TLS=auto and RunWisp serves HTTPS with a self-signed certificate.

The timezone database is built into the binary, so [daemon] timezone works on both bases without extra packages. The image also installs tzdata for your tasks, so date, PHP, Python, and similar tools get real zone data.

To use the host’s local time in tasks, set -e TZ=Europe/Helsinki or mount /etc/localtime:/etc/localtime:ro.

The image runs as root, because running a task as another user needs root. If no task sets user, you can run the container as another UID with user: in compose (or --user). That UID must be able to write the /var/lib/runwisp volume; the entrypoint tells you if it can’t.

The CLI inside the container talks to the daemon over the control socket and needs no password:

Terminal window
docker exec runwisp runwisp status
docker exec runwisp runwisp list
docker exec runwisp runwisp run hello

After editing runwisp.toml on the host, run docker exec runwisp runwisp reload (see Reload).