Skip to content

Container daemons experimental ​

A container daemon runs an image with Docker or Podman instead of a tool mise installs. It is declared with image and otherwise behaves like any other daemon: it has ports that follow the worktree, exports their variables, waits for a readiness check, and is started and stopped with mise daemons and daemons tasks.

Experimental

Daemons are experimental. Enable them with experimental = true under [settings]; see Requirements.

Declare a container ​

[daemons.postgres]
image = "postgres:18"
port = { auto = true, base = 5432 }
env = { POSTGRES_PASSWORD = "dev" }
data_mount = "/var/lib/postgresql"
ready_cmd = "{{ runtime }} exec {{ container }} pg_isready -U postgres"

In the primary checkout, mise daemons start postgres publishes the container's port 5432 on 127.0.0.1:5432. In a linked worktree the host port is the worktree's slot added to base, for example 5471, and the container still listens on 5432. The application reads POSTGRES_PORT in both. See Automatic ports for how slots work.

mise runs the container in the foreground with --rm, so its output is the daemon's log (mise daemons logs postgres) and the container is removed when it stops.

Keys ​

KeyMeaning
imageThe image to run. This is what makes the daemon a container; it cannot be combined with run, task, or preset.
commandThe container's command and arguments, replacing the image's default. A string is shell text, as run is; an array is passed as-is.
portThe host port for the container's main port: an integer, or { auto = true, base = 5432 }. "auto" alone needs a base, because an image has no default port.
container_portThe port the container listens on for port. Defaults to base for an automatic port and to the port itself for a fixed one.
portsMore published ports by name. See Several ports.
envVariables passed into the container. Values are set in the daemon's environment and given to the runtime by name, so they stay off the command line.
volumessource:target[:options] mounts. A source starting with ., / or ~ is a path (relative to the project root) and is created if missing; anything else is a named volume.
data_mountA path inside the container to keep data at. See Keep data.
data_dirThe host directory for data_mount, relative to the project root or absolute.
runtime"auto" (the default), "docker" or "podman". See Docker and Podman.
runtime_argsExtra arguments for <runtime> run, such as ["--shm-size=1g"], placed before the image.

init, depends, ready_port, ready_cmd, proxy, and the other daemon keys work as they do on a custom daemon. init runs on the host before the container starts. Like any custom daemon with a port, a container is given a proxy hostname; set proxy = false for a database or broker that does not speak HTTP.

Several ports ​

Give each additional published port a name. An integer publishes the same number on the host; a table sets the container port and a fixed host port, or turns on the same per-worktree allocation as port:

[daemons.cockroach]
image = "cockroachdb/cockroach:v25.1.0"
command = ["start-single-node", "--insecure"]
port = { auto = true, base = 26257 }
ports.http = { container = 8080, auto = true, base = 27000 }
ready_cmd = "{{ runtime }} exec {{ container }} cockroach sql --insecure -e 'select 1'"

An automatic named port uses the same slot as port (or the project root's slot when port is fixed), and its base defaults to the container port. With a slot of 7, this daemon publishes 26264 and 27007.

Each port is exported as <NAME>_PORT for port, and <NAME>_<PORT>_PORT for a named one: COCKROACH_PORT and COCKROACH_HTTP_PORT here. Every published port must be different. Ports are TCP and bound to 127.0.0.1 only, so nothing on your network can reach a container daemon.

Readiness ​

A published port opens on the host as soon as the container starts, before the server inside it can answer, so a ready_port check can pass early. Prefer a ready_cmd that asks the server. ready_cmd and health_cmd can use these variables, which mise fills in; other templates, such as pitchfork's {{ name }}, are left for pitchfork. runtime is a shell expression, so use it in the string form of a command or as the script of ["sh", "-c", "..."], not as another argument of an argument array:

VariableValue
{{ container }}The container's name
{{ runtime }}The runtime to call, as the daemon resolved it
{{ port }}The host port of port
{{ NAME }}The host port of the named port NAME under ports

For example, {{ runtime }} exec {{ container }} pg_isready asks the server in the container, and pg_isready -h 127.0.0.1 -p {{ port }} asks through the published port.

Keep data ​

data_mount stores a path of the container on the host. Without data_dir, the directory is in mise's state directory, which is per project root, so each worktree has its own, and mise daemons prune removes it with the project. Set data_dir to keep it in the checkout instead, as for presets:

[daemons.postgres]
image = "postgres:18"
port = { auto = true, base = 5432 }
data_mount = "/var/lib/postgresql"
data_dir = ".data/postgres"

For other mounts use volumes. On Linux, a directory Docker creates itself is owned by root; mise creates path sources first, so they belong to you. The image may still need runtime_args = ["--user", "..."] to write them.

Worktrees and cleanup ​

The container is named mise-<namespace>-<daemon>-<hash>, where the namespace carries the project and, in a linked worktree, the worktree, and <hash> is a short hash of the exact namespace and daemon name. Two checkouts, or two daemons whose names only read alike, therefore never share or fight over a container name, and each container is labelled dev.mise.daemon and dev.mise.project so you can find it with docker ps --filter label=....

When the daemon stops, mise runs <runtime> stop for the container and waits for it to exit, so the image's shutdown signal and grace period apply. If a previous run was killed rather than stopped and left its container behind, the next start removes the container with that name first. Nothing else is removed. Stopping a daemon while the image is still downloading cancels the start.

Docker and Podman ​

runtime = "auto" uses docker if it is on PATH, and otherwise podman. Set runtime = "podman" to pick one in the project. To choose per developer or use a docker-compatible CLI such as nerdctl, set MISE_CONTAINER_RUNTIME to its name or path; it overrides the declaration. mise looks the runtime up when the daemon starts, inside the project's tool environment, so a runtime installed by [tools] works. If it is missing, the start fails and the daemon log says so.

The runtime is not started for you: Docker's or Podman's own service (Docker Desktop, podman machine) must be running.

Limitations ​

  • Windows is not supported. The container's command is a POSIX shell script; on Windows the configuration still loads, and starting the daemon fails with a message.
  • Only TCP ports are published, and only on 127.0.0.1.
  • Container daemons are not shared servers, and mise does not pull or build images; docker run pulls the image on first use.
  • Variables from [env] are not passed into the container. Set env on the daemon.
  • Stopping gives the container a ten second grace period before the runtime kills it. mise sets the daemon's stop_signal timeout to 15 seconds so pitchfork waits for that; a stop_signal you set yourself is kept, and should be longer than ten seconds.
MIT LicenseCopyright © 2026jdx.dev