[compose.*]
A [compose.<alias>] block imports the services of an existing
docker-compose.yml. Each compose service becomes a RunWisp
service with captured logs, restarts,
notifications, and start/stop from the Web UI and API. The containers still run
under Docker. For a walkthrough, see
Migrating from docker-compose.
The smallest block imports every service from the compose file next to
runwisp.toml:
[compose.myapp]All block keys:
[compose.myapp]file = "./docker-compose.yml" # default: auto-discovered next to runwisp.tomlservices = ["api", "worker"] # keep only these; "-db" drops one instead; omit to import allimport = "services" # "services" (default) | "stack"group = "myapp" # UI group; defaults to the aliasproject_name = "myapp" # docker compose -p; defaults to the file's own projectprofiles = ["production"] # docker compose --profileenv_file = ["./.env.prod"] # docker compose --env-fileworking_dir = "/opt/myapp" # cwd for compose invocation; defaults to dir of `file`with_deps = false # default: --no-deps; true starts compose deps toopull = "missing" # "missing" (default) | "always" | "never"name_format = "{alias}.{service}" # RunWisp task naming; default showntype: string default: auto-discovered next to runwisp.toml
Path to the compose file. When unset, RunWisp looks in the runwisp.toml
directory for compose.yaml, compose.yml, docker-compose.yaml, then
docker-compose.yml (the same order docker compose uses).
[compose.myapp]file = "./docker-compose.yml"services
Section titled “services”type: string[] default: all services
Which compose services to import.
- Bare names (or
"+name") keep only the listed services. "-name"drops the listed services and keeps the rest.- You can’t mix the two styles in one list.
- A name that doesn’t exist (for example, one hidden behind an inactive profile) is an error.
[compose.myapp]services = ["api", "worker"]import
Section titled “import”type: enum default: "services"
How the compose file maps to RunWisp. See Import strategies.
[compose.myapp]import = "stack"Possible values:
services: one RunWisp service per compose servicestack: one RunWisp service that runs the whole project
type: string default: the alias
The Web UI and TUI sidebar group for every imported service.
[compose.myapp]group = "myapp"project_name
Section titled “project_name”type: string default: the compose file’s own project
The compose project name (docker compose -p). Unset, it is the project
docker compose up uses in the file’s directory: COMPOSE_PROJECT_NAME, the
file’s top-level name:, or the directory name.
[compose.myapp]project_name = "myapp"profiles
Section titled “profiles”type: string[]
Compose profiles to activate (docker compose --profile). A service with a
profiles: tag in the compose file doesn’t exist for RunWisp until you activate
its profile here.
The profiles are applied first, then the services filter picks
from the result:
[compose.myapp]profiles = ["workers"] # makes the profiled services visibleservices = ["worker"] # then keep only this one- Keeping a profiled service without activating its profile is an error
(
runwisp validatereports an unknown service). - Activating a profile with no
servicesfilter imports every visible service, profiled ones included.
To see which services a profile exposes, ask Compose:
docker compose --profile workers config --servicesenv_file
Section titled “env_file”type: string[]
Env files for the compose command itself (docker compose --env-file).
[compose.myapp]env_file = ["./.env.prod"]working_dir
Section titled “working_dir”type: string default: the directory of file
The directory the docker compose command runs in. It doesn’t change the
container’s working directory.
[compose.myapp]working_dir = "/opt/myapp"with_deps
Section titled “with_deps”type: boolean default: false
Whether starting a service also starts its compose depends_on services. With
false, RunWisp passes --no-deps.
[compose.myapp]with_deps = truetype: enum default: "missing"
When to pull the image before a start.
[compose.myapp]pull = "always"Possible values: missing, always, never.
name_format
Section titled “name_format”type: string default: "{alias}.{service}"
The RunWisp name of each imported service. With import = "services" it must
contain {service}, otherwise all names would be the same.
[compose.myapp]name_format = "{alias}-{service}"Block defaults
Section titled “Block defaults”[compose.<alias>.defaults] sets override keys once for every imported service.
A per-service sub-table still wins.
[compose.stack]services = ["worker-a", "worker-b", "worker-c"]
[compose.stack.defaults]restart = "always"restart_attempts = 5
[compose.stack.worker-a]restart = "on_failure" # only worker-a changes- Accepts the same keys as a per-service override.
- Order: imported default, then
defaults, then the per-service sub-table. - A
notifylist here adds routes to every service, on top of each service’s own. - A compose service named
defaultsis rejected. Rename it. - Not allowed with
import = "stack". Set keys on the block instead.
The imported restart default is on_failure, so a worker that exits with code
0 is not started again. For workers that used restart: unless-stopped in
Compose, set restart = "always" here.
Possible restart values:
never: don’t restart; one exit is finalon_failure: restart only after an exit thatfailurescounts as a failurealways: restart after any exit, likedocker compose up
Per-service overrides
Section titled “Per-service overrides”A [compose.<alias>.<service>] sub-table sets RunWisp keys for one compose
service. The name must be a compose service that passed the
services filter.
[compose.myapp]file = "./docker-compose.yml"
[compose.myapp.api]restart = "always"notify = ["slack-prod"]graceful_stop = "30s"keep_runs = 100env = { LOG_LEVEL = "info" } # added on top of the compose env
[compose.myapp.worker]restart = "on_failure"instances = 3Accepted keys:
- Identity:
group,description,manual_trigger - Supervision:
restart,restart_delay,restart_backoff,healthy_after,restart_attempts,priority,autostart,instances - Stopping:
timeout,graceful_stop,stop_signal - Failures and notifications:
failures,notify - Logs and retention:
log_max_size,log_on_full,keep_runs,keep_for - Environment:
env,env_file,secrets,secrets_file
Rejected:
run,compose_file,compose_service: the block decides what runs.on_overlap: a task-only key.shell,umask,env_base,user: these configure a host process, and a compose service runs in a container.working_dir: set it on the block.
A compose service with the same name as a block key (file, services,
import, group, project_name, profiles, env_file, working_dir,
with_deps, pull, name_format) can’t have a sub-table. The config is
rejected; rename the compose service.
Import strategies
Section titled “Import strategies”import = "services" (default)
Section titled “import = "services" (default)”Each compose service becomes one RunWisp service, named by
name_format. Each instance runs:
docker compose -f <file> -p <project> [--profile ...] [--env-file ...] run --rm --no-deps --service-ports --use-aliases --name <project>_<svc>_<instance_index> [--pull always|missing|never] -e RUNWISP_INSTANCE_INDEX -e KEY ... # your env + secrets, values read from RunWisp's own environment <svc>--service-ports and --use-aliases give the container the same ports and
network aliases as in the compose file. --rm removes the container on exit, so
every restart starts from a fresh container.
Since 1.8, an instance whose container is already running from
docker compose up is not started twice. RunWisp attaches to that container
instead: the run shows its output from then on (through docker logs -f) and
ends with its exit code, and the run log says it attached. A stop or restart,
including stopping the daemon, stops the up container, so the next start runs
RunWisp’s own. The Nth instance attaches to the Nth up container by name.
Tasks always start their own container.
import = "stack"
Section titled “import = "stack"”One RunWisp service named <alias> runs the whole project:
docker compose -f <file> -p <project> up --abort-on-container-exit --no-log-prefix- Logs from all containers go into one run.
- The run ends when any container exits.
- Per-service sub-tables,
defaults, theservicesfilter, andinstancesare rejected.
Imported defaults
Section titled “Imported defaults”Imported services use these defaults instead of the [services.*] ones. A
per-service sub-table overrides any of them.
| Key | Default for imported services |
|---|---|
| kind | service |
restart |
on_failure |
group |
the alias |
pull |
missing |
graceful_stop |
the compose stop_grace_period if set, else the daemon default |
| Container name | {project}_{service}_{index} (matches docker compose ps; docker logs works). An overlapping run adds _{run id}. |
| RunWisp name | {alias}.{service} (see name_format) |
Instances
Section titled “Instances”instances = N in a per-service sub-table starts N parallel docker compose run
containers.
- Each gets
RUNWISP_INSTANCE_INDEX(0toN-1); use it to split the work. - The container name ends with the index, so
docker compose pslists each one. - All instances get the same env. For different env per instance, define one compose service per instance.
Single compose services
Section titled “Single compose services”A [services.*] or [tasks.*] table can run one compose service without a
[compose.*] block:
# Long-running service from a compose file:[services.api]compose_file = "./docker-compose.yml"compose_service = "api" # defaults to the table key
# Cron task that runs a compose service once:[tasks.nightly-backup]cron = "0 3 * * *"compose_file = "./docker-compose.yml"compose_service = "backup"Both run in the compose file’s own project, which is also the default for a
[compose.*] block’s project_name; see
compose_file.
Running a command in a service: compose_mode
Section titled “Running a command in a service: compose_mode”With run set next to
compose_service, RunWisp runs your
command inside the service’s running container (docker compose exec). No new
container is started, and the command gets that container’s env, code, and
credentials.
[tasks.laravel-schedule]cron = "* * * * *"compose_file = "/srv/myapp/compose.yaml"compose_service = "app"run = "php artisan schedule:run"compose_mode picks the mode:
| Value | What happens |
|---|---|
exec |
docker compose exec in the running container. Requires run. Default when run is set. |
run |
docker compose run --rm in a new container, without the service’s ports or network aliases. Runs your run command if set, else the service’s own command. Default when run is unset. |
Without compose_file
Section titled “Without compose_file”since: 1.7
When RunWisp runs as a service of the same compose project, an exec task can
leave out compose_file. RunWisp reads the project from its own container’s
labels, so it needs only the Docker socket, not the project directory.
[tasks.laravel-schedule]cron = "* * * * *"compose_service = "app"run = "php artisan schedule:run"compose_mode = "run" still needs compose_file. runwisp validate warns
when RunWisp doesn’t run in a container, since every run would fail.
Limits of exec:
- The container must be running. If it isn’t, the run fails, and its log names the compose project RunWisp searched (see If RunWisp runs in a container).
timeoutcan’t stop the process inside the container. Docker can’t cancel an exec, so RunWisp stops only the local client. Add a limit inside the command too:run = "timeout 3600 php artisan long-job". RunWisp warns at startup about a long-running service inexecmode, because each restart leaves another process behind.- The command runs under the container’s
/bin/shwith fail-fast, likerunon the host.shellis not accepted.
Supervision
Section titled “Supervision”- RunWisp decides restarts, using
restartand the backoff keys. The compose file’srestart:setting is ignored. - If the
dockerCLI is missing or the Docker daemon is unreachable, the services stay visible, and their runs fail with a system log message.