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:/etc/runwisp
- runwisp-data:/var/lib/runwisp
volumes:
runwisp-data:

Run docker compose up -d, open http://localhost:9477, and log in with the password. On first start, RunWisp writes a starter config with one example task to ./runwisp/runwisp.toml. Edit it and run docker compose exec runwisp runwisp reload to apply your changes.

The same with docker run:

Terminal window
docker run -d --name runwisp \
-p 9477:9477 \
-e RUNWISP_PASSWORD=change-me \
-v ./runwisp:/etc/runwisp \
-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 at /etc/runwisp/runwisp.toml. On first start, if the file is missing and /etc/runwisp is a writable mount, RunWisp writes the starter config there. Otherwise a missing config stops the container, including when the data volume already holds a database: that means the config volume was mounted in the wrong place, and starting with the starter would quietly drop your tasks.

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:/etc/runwisp 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: the directory holding runwisp.toml. RunWisp only writes to it once, to create the starter, so you can mount it :ro after that. Put more config files next to runwisp.toml and load them with include. Mount the directory rather than the single file: many editors save by replacing the file, and a single-file mount keeps showing the container the old copy, so reload misses your edit. If ./runwisp doesn’t exist yet, Docker creates it owned by root; run mkdir runwisp first to own it.
  • /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. A task that calls a missing tool fails with pg_dump: not found in its log. To check what an image contains:

Terminal window
docker run --rm --entrypoint sh runwisp/runwisp:latest -c 'command -v pg_dump curl'

To add tools, extend the image:

FROM runwisp/runwisp:latest
RUN apk add --no-cache postgresql17-client curl

On a -debian tag, use apt-get install. The entrypoint, healthcheck, and environment defaults are kept. To run docker itself from a task, 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).