[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.
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.