docker-compose.nix

May 14, 2025



docker-compose.nix enables nix-defined or imported docker-compose.yml services with secrets (e.g., sops-nix) integration and optional systemd service.

Nixos has decent support for containers (see oci-containers), and ideally you package the app and write a nixos module to go full native, but sometimes a project's preferred distribution and installation method is by docker-compose. docker-compose.nix is like a halfway point; in its most basic usage, you provide a docker-compose.yml and it gets copied to /var/lib/[service-name] . You can either start it yourself with docker-compose up -d, or you can enable a systemd service to do it for you.

I used to do this via home-manager's home.file directive, then I wrote this for my systems that don't really need home-manager.

Secrets

Docker itself has support for secrets provided by environment variables or interpolation; this module provides some additional wiring to that effect.

If envSubstFile is provided, then any VAR=VALUE definition found therein will be used to replace $VAR found in docker-compose.yml with VALUE. The same substitution is done for files provided by additionalFiles; the use case here is where a docker-compose definition mounts a separate config file as a volume, and that config format does not allow for environment variable usage for secrets.

Example: frigate

An example for a combined frigate and frigate-notify service definition, both of which actually do allow for environment variables for passwords, but for purposes of demonstration:

# frigate.nix
{ config, ... }:
{
  sops.secrets.frigate-env = {};

  services.docker-compose = {
    enable = true;
    services = {
      frigate = {
        composeFile = ./docker-compose.yml;
        envSubstFile = config.sops.secrets.frigate-env.path;
        additionalFiles = [
          ./config-frigate.yml
          ./config-notify.yml
        ];
      };
    };
  };
}

sops.secrets.frigate-env is a multi-line yaml definition:

frigate-env: |
    FRIGATE_MQTT_PASSWORD=asdfasdfasdfasdf
    FRIGATE_NOTIFY_MQTT_PASSWORD=asdfasdfasdfasdf
    FRIGATE_NOTIFY_NTFY_TOKEN=asdfasdfasdfasdf

In docker-compose.yml, note volume mappings of config-frigate.yml and config-notify.yml, which are included in the nix config up above. The relevant lines in docker-compose:

# docker-compose.yml
services:
  frigate:
    image: ghcr.io/blakeblackshear/frigate:stable
    volumes:
      - ./config-frigate.yml:/config/config.yml

  frigate-notify:
    image: ghcr.io/0x2142/frigate-notify:latest
    volumes:
      - ./config-notify.yml:/app/config.yml

Relevant parts of the config files:

# config-frigate.yml
...
mqtt:
  user: frigate
  password: $FRIGATE_MQTT_PASSWORD
...
# config-notify.yml
...
  mqtt: 
    username: frigate-notify
    password: $FRIGATE_NOTIFY_MQTT_PASSWORD
...

You can specify a secrets file in env_file attribute of docker-compose -- the documentation says the path is relative but in my experience it can also be absolute. If I had gone that route, then I'd probably do

# docker-compose.yml
services:
  frigate:
    env_file: /run/secrets/frigate-env
  
  frigate-notify:
    env_file: /run/secrets/frigate-env

with the appropriate adjustments to the env var names.

Good luck!

https://blog.femtodata.com/posts/feed.xml