jdx/mise · error

bootstrap compose project '{name}' services cannot be combin

Error message

bootstrap compose project '{name}' services cannot be combined with state = "absent" because compose down removes the entire project

What it means

With `state = "absent"`, mise converges by running `docker compose down`, which removes the entire project — every service, network, and (per flags) volumes — not just the services you list. Because per-service selection cannot be honored on removal, combining `services` with absent state is rejected at config parse time.

Source

Thrown at src/system/compose.rs:287

impl ComposeRequest {
    fn from_toml(name: String, config: ComposeTomlConfig) -> Result<Self> {
        if name.is_empty() {
            bail!("bootstrap compose project names cannot be empty");
        }
        if !config.project_dir.is_absolute() {
            bail!(
                "bootstrap compose project '{name}' project_dir must be absolute: {}",
                config.project_dir.display()
            );
        }
        validate_project_name(config.project_name.as_deref())?;
        validate_values("profile", &config.profiles)?;
        validate_values("service", &config.services)?;
        validate_values("oneshot service", &config.oneshot)?;
        validate_command("command", &config.command)?;
        validate_command("engine_command", &config.engine_command)?;
        if config.state == ComposeState::Absent && !config.services.is_empty() {
            bail!(
                "bootstrap compose project '{name}' services cannot be combined with state = \"absent\" because compose down removes the entire project"
            );
        }
        if !config.wait && config.wait_timeout.is_some() {
            bail!("bootstrap compose project '{name}' wait_timeout requires wait = true");
        }
        if config.wait_timeout == Some(0) {
            bail!("bootstrap compose project '{name}' wait_timeout must be greater than zero");
        }
        if config.state != ComposeState::Absent
            && (config.down_volumes || config.down_images.is_some())
        {
            bail!(
                "bootstrap compose project '{name}' down_volumes and down_images require state = \"absent\""
            );
        }
        if config.state != ComposeState::Running && config.renew_anonymous_volumes {
            bail!(

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. To remove the whole project: delete the `services`/`oneshot` lists from the absent-state table.
  2. To keep only selected services running: set `state = "running"` and keep the `services` list.
  3. If you need a separate teardown definition, use a distinct `[bootstrap.compose.<name>]` table with `state = "absent"` and no services.

Example fix

# before
[bootstrap.compose.web]
project_dir = "/srv/web"
state = "absent"
services = ["api"]

# after
[bootstrap.compose.web]
project_dir = "/srv/web"
state = "absent"
Defensive patterns

Strategy: validation

Validate before calling

if cfg.state == ComposeState::Absent {
    assert!(cfg.services.is_empty() && cfg.oneshot.is_empty(),
        "services/oneshot cannot be combined with state = absent");
}

Prevention

When it happens

Trigger: A compose table sets both `state = "absent"` and a non-empty `services = [...]` (or oneshot services) list. `ComposeRequest::from_toml` bails during `mise bootstrap compose plan`/`apply` config parsing.

Common situations: Reusing one project block for both 'bring up selected services' and 'tear down' by flipping `state` while leaving `services` populated; copy-paste of a running project's config with only the state changed.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/751069b6958e9943. Report an issue: GitHub.