# compose2nix, turning a Docker Compose file into a NixOS configuration

> compose2nix reads a Docker Compose project and writes NixOS configuration, one oci-container config and systemd service per Compose service, so NixOS manages each container natively instead of restarting a whole Compose stack on any change. It handles Docker and Podman and it integrates with agenix and sops-nix for secrets.

**aksiksi/compose2nix** — Generate a NixOS config from a Docker Compose project.

- Repository: https://github.com/aksiksi/compose2nix
- Stars: 913 · Forks: 23
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/aksiksi-compose2nix

## Why a Compose file on NixOS is a problem worth solving

compose2nix is a tool to automatically generate a NixOS config from a Docker Compose project, and the README explains the problem in a paragraph that is worth reading in full.

Running a Docker Compose stack or project on NixOS is not well supported. One approach is to define a systemd service that runs docker-compose up on start and docker compose down on stop. The problem is that changes to individual services are not visible to NixOS, which means NixOS has to restart the systemd service on any change to the Compose file. That can be mitigated with a systemd reload handler, but the documentation is blunt that it remains finicky to work with and will always stay opaque to NixOS.

The second argument is redundancy. Using Docker Compose on NixOS is fairly redundant, because the features Compose gives you are available natively on NixOS.

Both arguments point at the same conclusion. Compose is a convenient orchestration layer, and NixOS already has an orchestration layer: systemd and the NixOS module system. Running one inside the other means two layers of state that disagree about what is running. The tool removes the inner layer, so the answer to what is running is always something NixOS knows about.

The audience is therefore narrow but real: people with a Compose file, a NixOS machine, and the frustration of not being able to change one service without restarting the stack.

## One oci-container config and one systemd service per service

compose2nix takes your existing Docker Compose files and converts each YAML service definition into an oci-container config, and it also sets up systemd services to create all the networks and volumes that are part of the Compose project. The oci-container module is the NixOS option for running containers, and generating them means NixOS itself starts, stops and restarts the containers.

The benefits listed in the README make the trade explicit. Docker and Podman are both supported out of the box. Each Compose service maps into a systemd service that is natively managed by NixOS. A change to one container service only impacts that container and any of its dependents, which is the specific pain the systemd-wrapper approach cannot fix. Generated systemd services can be extended from your NixOS config, so the generated file is a starting point rather than a sealed artefact. And compose2nix supports setting additional systemd service and unit options through Docker Compose labels, with the compose2nix.systemd. label searched for in the samples.

The label mechanism is the interesting one, because it is how the two formats are joined without inventing a new syntax. Anything you cannot express in Compose, you express as a label on the service in Compose, and the generator turns it into the systemd unit option you wanted.

One more benefit comes for free, and it is a real one. compose2nix uses the same library that the Docker CLI relies on under the hood, which is compose-go, so you also get Compose file validation and syntax checking for free. The Go module list confirms it, with github.com/compose-spec/compose-go/v2 as a direct dependency at version 2.14.0, alongside sprig for templating, godotenv for dotenv parsing and yaml.v3.

## Three ways to get the CLI, and one way to run it

The recommended way to run the tool is through nix run, and the README gives three variants of it:

```bash
# Latest
nix run github:aksiksi/compose2nix -- -h

# Specific version
nix run github:aksiksi/compose2nix/v0.3.0 -- -h

# Specific commit
nix run github:aksiksi/compose2nix/0c38d282d6662fc902fca7ef5b33e889f9e3e59a -- -h
```

The three forms matter more than they look. Running the latest gives you the current development state, pinning a version tag gives you a reproducible tool, and pinning a commit hash gives you a specific tree that may predate the last tag.

The alternatives are installation from nixpkgs, where the package is pkgs.compose2nix, and adding the project as a flake input, in which case the input follows nixpkgs and can be pinned to a version tag:

```nix
compose2nix.url = "github:aksiksi/compose2nix";
compose2nix.inputs.nixpkgs.follows = "nixpkgs";
```

With the flake in place, the package is added to a NixOS config as inputs.compose2nix.packages.x86_64-linux.default inside environment.systemPackages.

Running the tool is a single command. The project must either be passed on the command line or set in the Compose file's top-level name field:

```bash
compose2nix -project=myproject
```

By default it looks for docker-compose.yml in the current directory and writes the NixOS config to docker-compose.nix. The input and output files can be pointed elsewhere with the flags used elsewhere in the tool, such as --inputs and --sops_file in the sops-nix workflow.

## Environment files are handled at two different stages

The environment file handling is the part of the documentation with the most room for a mistake, and the README separates it deliberately.

The -env_files flag accepts comma-separated paths to files used for build-time interpolation. Those files must be available when you run compose2nix, and variables from the process environment take precedence over variables in those files. This is the classic Compose behaviour, where an env_file feeds variable substitution into the Compose file itself.

The -include_env_files flag accepts comma-separated paths that are added to environmentFiles for all generated containers. Those files are read at runtime and do not need to exist on the machine running compose2nix.

That distinction is the whole point, and it maps onto how Compose projects are usually built. A build-time file might hold the image tag you are testing, and it belongs in your repository. A runtime file holds credentials, and it should not be on the machine that generates the configuration, let alone in the generated Nix file.

If you want a file used at both stages, pass it to both flags:

```bash
compose2nix -env_files=app.env -include_env_files=app.env
```

The migration notes for the older flags are worth reading too. Runtime-only usage of the form -env_files=secrets.env -include_env_files=true -env_files_only=true should be replaced with -include_env_files=secrets.env, keeping -env_files only for files needed for build-time interpolation. Boolean values for -include_env_files and for the deprecated -env_files_only and -ignore_missing_env_files flags produce a migration error rather than silently changing behaviour, and runtime-only files do not require -ignore_missing_env_files.

## Secrets through agenix or sops-nix

Two integrations with the Nix secret ecosystem are documented, and they solve the problem that the runtime environment file flag creates.

agenix decrypts secrets and places them in /run/agenix/. To feed that into your Nix config, you place all the secret environment variables in the encrypted env file, mark the decrypted env file as readable by the user running compose2nix, and pass the path to -include_env_files:

```bash
compose2nix --include_env_files=/run/agenix/my-env-file.env
```

The note repeats that runtime env files do not need to exist on the machine running compose2nix, which is the property that makes this safe to run in CI.

The sops-nix integration works the other way round, by referring to secrets your NixOS configuration already defines. You add a compose2nix.settings.sops.secrets label with comma-separated secret names to the Compose services:

```yaml
services:
  webapp:
    image: nginx:latest
    labels:
      - "compose2nix.settings.sops.secrets=example.env,some-folder/example-2.env"
```

and then point the tool at your encrypted secrets YAML file:

```bash
compose2nix \
  --inputs docker-compose.yml \
  --sops_file ./secrets/secrets.yaml
```

The generated configuration then references your existing sops secrets as environment files rather than copying their values into the output, which is the difference between a generated file you can commit and one you cannot.

## The test suite boots real NixOS virtual machines

The build and test tooling in the repository shows how seriously the project takes the claim that the generated configuration actually works on NixOS.

The Makefile has a build target that compiles the Go binary into bin/, a test target running go test -v, a coverage target writing a coverage profile, and a flake target building the Nix package. There is an update-deps target that updates nixpkgs and the Go module dependencies and then runs the tests, and a shell target that drops you into nix develop with zsh and every build dependency.

The interesting one is nixos-test. The comment above it says it brings up two NixOS virtual machines, one for Docker and one for Podman, and ensures that the compose2nix generated config works when loaded into NixOS. The integration check is built with nix build .#checks.x86_64-linux.integrationTest, with a verbose variant that keeps the build log. There is a nixos-test/ directory with an update script that the target runs first.

So the claim that the same generated configuration works for both Docker and Podman is not only documented, it is checked on every run by booting two virtual machines. The repository also has a test workflow badge and a NixOS workflow badge at the top of the README, plus codecov, and the testdata/ directory holds the sample input and the expected Docker and Podman outputs.

The Go side of the code is split into files named after what they do: compose.go for the Compose parsing, nix.go for the Nix generation, systemd.go for the units, sops.go for the secret integration, template.go for the rendering, and helpers.go, with templates/ holding the templates themselves.

## Conclusion

compose2nix suits a team that has an existing Compose file and has moved its hosts to NixOS, and who want the containers to be managed by systemd instead of by a Compose wrapper. It does not suit a team that wants to keep Compose as the runtime, or one whose Compose files use features the generator does not map. Before you adopt it, generate a config from your real Compose file and diff it against what you expect, because the mapping is one way and Compose features do not all survive it, and read the environment file documentation carefully, since the two flags for env files are applied at different stages and mixing them up either leaks build-time values into runtime containers or fails the build.

## FAQ

### What does compose2nix generate from a Docker Compose file?

It converts each YAML service definition into an oci-container config and sets up systemd services to create the networks and volumes that are part of the Compose project. Because it uses the same compose-go library the Docker CLI relies on, Compose file validation and syntax checking come with it.

### How do I run compose2nix?

The recommended route is nix run github:aksiksi/compose2nix -- -h, with variants for a specific version tag and a specific commit hash. You can also install pkgs.compose2nix from nixpkgs or add the project as a flake input. The tool needs the project name passed with -project= or set in the Compose file's top-level name, and it looks for docker-compose.yml in the current directory and writes docker-compose.nix.

### What is the difference between -env_files and -include_env_files?

The -env_files flag takes comma-separated paths used for build-time interpolation, and those files must exist when compose2nix runs, with the process environment taking precedence. The -include_env_files flag adds paths to environmentFiles for all generated containers, and those are read at runtime and do not need to exist on the machine running the tool.

### Does compose2nix support Docker and Podman?

Yes, both are supported out of the box. The integration test in the repository brings up two NixOS virtual machines, one for Docker and one for Podman, and checks that the generated configuration works when loaded into NixOS.

### Can compose2nix use secrets from sops-nix or agenix?

Yes. With agenix, you pass the decrypted env file under /run/agenix to -include_env_files. With sops-nix, you add a compose2nix.settings.sops.secrets label to the Compose service listing the secret names and run compose2nix with --sops_file pointing at your encrypted secrets YAML, so the generated config references the secrets rather than copying their values.

## Sources

- [aksiksi/compose2nix on GitHub](https://github.com/aksiksi/compose2nix)
- [Issues](https://github.com/aksiksi/compose2nix/issues)
- [License: MIT](https://github.com/aksiksi/compose2nix/blob/main/LICENSE)
- [README](https://github.com/aksiksi/compose2nix/blob/main/README.md)
- [Releases](https://github.com/aksiksi/compose2nix/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/aksiksi-compose2nix
