├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── flakehub-publish-tagged.yml │ ├── nixos.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── compose.go ├── flake.lock ├── flake.nix ├── go.mod ├── go.sum ├── helpers.go ├── main.go ├── nix.go ├── nix_test.go ├── nixos-test ├── compose.yml ├── docker-compose.nix ├── podman-compose.nix ├── test.nix └── update.sh ├── systemd.go ├── template.go ├── templates ├── build.nix.tmpl ├── config.nix.tmpl ├── container.nix.tmpl ├── main.nix.tmpl ├── network.nix.tmpl └── volume.nix.tmpl └── testdata ├── TestAutoStart.compose.yml ├── TestAutoStart.docker.nix ├── TestAutoStart.podman.nix ├── TestBasic.docker.nix ├── TestBasic.podman.nix ├── TestBasicAutoFormat.docker.nix ├── TestBasicAutoFormat.podman.nix ├── TestBuildSpec.compose.yml ├── TestBuildSpec.docker.nix ├── TestBuildSpec.podman.nix ├── TestBuildSpec_BuildEnabled.compose.yml ├── TestBuildSpec_BuildEnabled.docker.nix ├── TestBuildSpec_BuildEnabled.podman.nix ├── TestCommandAndEntrypoint.compose.yml ├── TestCommandAndEntrypoint.docker.nix ├── TestCommandAndEntrypoint.podman.nix ├── TestComposeEnvFiles.compose.yml ├── TestComposeEnvFiles.docker.nix ├── TestComposeEnvFiles.podman.nix ├── TestDeployDevices.compose.yml ├── TestDeployDevices.docker.nix ├── TestDeployDevices.podman.nix ├── TestEmptyEnv.compose.yml ├── TestEmptyEnv.docker.nix ├── TestEmptyEnv.podman.nix ├── TestEnableOption.docker.nix ├── TestEnableOption.podman.nix ├── TestEnableOption_WithHeader.docker.nix ├── TestEnableOption_WithHeader.podman.nix ├── TestEnvFiles.compose.yml ├── TestEnvFiles.docker.nix ├── TestEnvFiles.podman.nix ├── TestEnvFilesOnly.compose.yml ├── TestEnvFilesOnly.docker.nix ├── TestEnvFilesOnly.podman.nix ├── TestEscapeChars.compose.yml ├── TestEscapeChars.docker.nix ├── TestEscapeChars.podman.nix ├── TestExternalNetworksAndVolumes.compose.yml ├── TestExternalNetworksAndVolumes.docker.nix ├── TestExternalNetworksAndVolumes.podman.nix ├── TestMacvlanSupport.compose.yml ├── TestMacvlanSupport.docker.nix ├── TestMacvlanSupport.podman.nix ├── TestMultipleNetworks.compose.yml ├── TestMultipleNetworks.docker.nix ├── TestMultipleNetworks.podman.nix ├── TestNetworkAndVolumeNames.compose.yml ├── TestNetworkAndVolumeNames.docker.nix ├── TestNetworkAndVolumeNames.podman.nix ├── TestNetworkSettings.compose.yml ├── TestNetworkSettings.docker.nix ├── TestNetworkSettings.podman.nix ├── TestNoCreateRootTarget.compose.yml ├── TestNoCreateRootTarget.docker.nix ├── TestNoCreateRootTarget.podman.nix ├── TestNoRestart.compose.yml ├── TestNoRestart.docker.nix ├── TestNoRestart.podman.nix ├── TestNoWriteNixSetup.docker.nix ├── TestNoWriteNixSetup.podman.nix ├── TestOptionPrefix.docker.nix ├── TestOptionPrefix.podman.nix ├── TestOverrideSystemdStopTimeout.docker.nix ├── TestOverrideSystemdStopTimeout.podman.nix ├── TestProject.docker.nix ├── TestProject.podman.nix ├── TestRelativeServiceVolumes.compose.yml ├── TestRelativeServiceVolumes.docker.nix ├── TestRelativeServiceVolumes.podman.nix ├── TestRelativeServiceVolumes_CurrentDirectory.docker.nix ├── TestRelativeServiceVolumes_CurrentDirectory.podman.nix ├── TestRemoveVolumes.docker.nix ├── TestRemoveVolumes.podman.nix ├── TestSystemdMount.docker.nix ├── TestSystemdMount.podman.nix ├── TestUnusedResources.compose.yml ├── TestUnusedResources.docker.nix ├── TestUnusedResources.podman.nix ├── TestUpheldBy.docker.nix ├── TestUpheldBy.podman.nix ├── compose.yml ├── first.env ├── input.env └── second.env /.gitattributes: -------------------------------------------------------------------------------- 1 | testdata/* linguist-detectable=false 2 | nixos-test/* linguist-detectable=false 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/flakehub-publish-tagged.yml: -------------------------------------------------------------------------------- 1 | name: "Publish tags to FlakeHub" 2 | on: 3 | push: 4 | tags: 5 | - "v?[0-9]+.[0-9]+.[0-9]+*" 6 | workflow_dispatch: 7 | inputs: 8 | tag: 9 | description: "The existing tag to publish to FlakeHub" 10 | type: "string" 11 | required: true 12 | 13 | jobs: 14 | flakehub-publish: 15 | runs-on: "ubuntu-latest" 16 | permissions: 17 | id-token: "write" 18 | contents: "read" 19 | steps: 20 | - uses: "actions/checkout@v4" 21 | with: 22 | ref: "${{ (inputs.tag != null) && format('refs/tags/{0}', inputs.tag) || '' }}" 23 | - uses: "DeterminateSystems/nix-installer-action@main" 24 | - uses: "DeterminateSystems/flakehub-push@main" 25 | with: 26 | visibility: "public" 27 | name: "aksiksi/compose2nix" 28 | tag: "${{ inputs.tag }}" 29 | include-output-paths: true 30 | -------------------------------------------------------------------------------- /.github/workflows/nixos.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/skatolo/gh-actions-nixos-tests 2 | name: NixOS 3 | 4 | on: 5 | push: 6 | paths-ignore: 7 | - '**/*.md' 8 | branches: 9 | - main 10 | pull_request: 11 | paths-ignore: 12 | - '**/*.md' 13 | 14 | jobs: 15 | nixos-test: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-go@v4 20 | with: 21 | go-version: '>=1.21' 22 | - uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.10' 25 | - uses: cachix/install-nix-action@v22 26 | with: 27 | extra_nix_config: "system-features = nixos-test benchmark big-parallel kvm" 28 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 29 | nix_path: nixpkgs=channel:nixos-unstable 30 | - uses: DeterminateSystems/magic-nix-cache-action@main 31 | - name: NixOS test 32 | run: | 33 | sudo chmod o+rw /dev/kvm 34 | make nixos-test 35 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**/*.md' 7 | branches: 8 | - main 9 | pull_request: 10 | paths-ignore: 11 | - '**/*.md' 12 | 13 | jobs: 14 | linux-test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-go@v4 19 | with: 20 | go-version: '>=1.21' 21 | - run: go version 22 | - name: Test 23 | run: make coverage 24 | - name: Upload coverage 25 | uses: codecov/codecov-action@v4 26 | with: 27 | fail_ci_if_error: true 28 | files: coverage.out 29 | token: ${{ secrets.CODECOV_TOKEN }} 30 | linux-test-with-nixfmt: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | - uses: cachix/install-nix-action@v22 35 | with: 36 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 37 | nix_path: nixpkgs=channel:nixos-unstable 38 | - uses: DeterminateSystems/magic-nix-cache-action@main 39 | - name: Run tests with nixfmt 40 | run: nix develop .#ci --command make coverage 41 | - name: Upload coverage 42 | uses: codecov/codecov-action@v4 43 | with: 44 | fail_ci_if_error: true 45 | files: coverage.out 46 | token: ${{ secrets.CODECOV_TOKEN }} 47 | linux-build-flake: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: cachix/install-nix-action@v22 52 | with: 53 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 54 | nix_path: nixpkgs=channel:nixos-unstable 55 | - uses: DeterminateSystems/magic-nix-cache-action@main 56 | - run: make flake 57 | 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | /docker-compose.yml 3 | /docker-compose.nix 4 | out.nix* 5 | *.env 6 | !testdata/*.env 7 | result 8 | .pre-commit-config.yaml 9 | coverage.out 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Assil Ksiksi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mkdir -p bin/ && go build -o bin/ . 3 | 4 | test: 5 | go test -v 6 | 7 | coverage: 8 | go test -v -covermode=count -coverprofile=coverage.out 9 | 10 | flake: 11 | nix build -L .#packages.x86_64-linux.default 12 | 13 | # Updates nixpkgs, Go module deps, and runs tests. 14 | update-deps: 15 | go get -u github.com/Masterminds/sprig/v3 github.com/compose-spec/compose-go/v2 github.com/joho/godotenv 16 | go mod tidy 17 | nix flake lock --update-input nixpkgs 18 | make flake 19 | make shell 20 | make test 21 | 22 | # Pulls in all build dependencies into a shell. 23 | shell: 24 | nix develop -c zsh 25 | 26 | # This brings up two NixOS VMs - one for Docker and one for Podman - and ensures that 27 | # the compose2nix generated config works when loaded into NixOS. 28 | nixos-test: 29 | ./nixos-test/update.sh 30 | nix build -L .#checks.x86_64-linux.integrationTest 31 | 32 | .PHONY: build coverage flake nixos-test test 33 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1652776076, 6 | "narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nix-pre-commit": { 19 | "inputs": { 20 | "flake-utils": "flake-utils", 21 | "nixpkgs": [ 22 | "onchg", 23 | "nixpkgs" 24 | ] 25 | }, 26 | "locked": { 27 | "lastModified": 1653259102, 28 | "narHash": "sha256-XfCEu4zur/N2Dk4v8wFiQAgJ7bgNqPqwWp1vBXkeczM=", 29 | "owner": "jmgilman", 30 | "repo": "nix-pre-commit", 31 | "rev": "6a99b2711c7eac9960939d8eb91e84322b22d50c", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "jmgilman", 36 | "repo": "nix-pre-commit", 37 | "type": "github" 38 | } 39 | }, 40 | "nixpkgs": { 41 | "locked": { 42 | "lastModified": 1732014248, 43 | "narHash": "sha256-y/MEyuJ5oBWrWAic/14LaIr/u5E0wRVzyYsouYY3W6w=", 44 | "owner": "NixOS", 45 | "repo": "nixpkgs", 46 | "rev": "23e89b7da85c3640bbc2173fe04f4bd114342367", 47 | "type": "github" 48 | }, 49 | "original": { 50 | "owner": "NixOS", 51 | "ref": "nixos-unstable", 52 | "repo": "nixpkgs", 53 | "type": "github" 54 | } 55 | }, 56 | "onchg": { 57 | "inputs": { 58 | "nix-pre-commit": "nix-pre-commit", 59 | "nixpkgs": [ 60 | "nixpkgs" 61 | ] 62 | }, 63 | "locked": { 64 | "lastModified": 1720368454, 65 | "narHash": "sha256-NUSw3G2gsQX8/G64/pDBb1oitM+x13m7nFRvpiI4a+s=", 66 | "owner": "aksiksi", 67 | "repo": "onchg-rs", 68 | "rev": "c42b693d10920874b3644ef1502e33318409d69c", 69 | "type": "github" 70 | }, 71 | "original": { 72 | "owner": "aksiksi", 73 | "repo": "onchg-rs", 74 | "type": "github" 75 | } 76 | }, 77 | "root": { 78 | "inputs": { 79 | "nixpkgs": "nixpkgs", 80 | "onchg": "onchg" 81 | } 82 | } 83 | }, 84 | "root": "root", 85 | "version": 7 86 | } 87 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A tool to automatically generate a NixOS config from a Docker Compose project."; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | onchg.url = "github:aksiksi/onchg-rs"; 7 | onchg.inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | 10 | outputs = { self, nixpkgs, onchg, ... }: let 11 | supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; 12 | forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 13 | pkgsFor = system: nixpkgs.legacyPackages.${system}; 14 | pname = "compose2nix"; 15 | owner = "aksiksi"; 16 | # LINT.OnChange(version) 17 | version = "0.3.2-pre"; 18 | # LINT.ThenChange(main.go:version) 19 | in { 20 | # Nix package 21 | packages = forAllSystems (system: 22 | let pkgs = pkgsFor system; in { 23 | default = pkgs.buildGoModule { 24 | inherit pname; 25 | inherit version; 26 | src = ./.; 27 | vendorHash = "sha256-fqJTZPDaWJB05nyLPif968Ek2pJQzkKpr6lrdgSY9MM="; 28 | }; 29 | } 30 | ); 31 | 32 | # Development shell 33 | devShells = forAllSystems (system: 34 | let pkgs = pkgsFor system; in { 35 | default = pkgs.mkShell { 36 | buildInputs = [ pkgs.go pkgs.gopls pkgs.nixfmt-rfc-style ]; 37 | # Add a Git pre-commit hook. 38 | shellHook = onchg.shellHook.${system}; 39 | }; 40 | ci = pkgs.mkShell { 41 | # We already have Go installed. 42 | buildInputs = [ pkgs.nixfmt-rfc-style ]; 43 | }; 44 | } 45 | ); 46 | 47 | # Run: 48 | # nix build .#checks.x86_64-linux.integrationTest 49 | # To run interactively: 50 | # nix build .#checks.x86_64-linux.integrationTest.driverInteractive 51 | # See: https://nixos.org/manual/nixos/stable/index.html#sec-running-nixos-tests-interactively 52 | checks.x86_64-linux = let 53 | pkgs = nixpkgs.legacyPackages.x86_64-linux; 54 | in { 55 | # This test is meant to be run by nixos-test/test.sh. 56 | # https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests 57 | # https://nix.dev/tutorials/nixos/integration-testing-using-virtual-machines 58 | integrationTest = pkgs.nixosTest (import ./nixos-test/test.nix); 59 | }; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // LINT.OnChange() 2 | module github.com/aksiksi/compose2nix 3 | 4 | go 1.21 5 | 6 | require ( 7 | github.com/Masterminds/sprig/v3 v3.3.0 8 | github.com/compose-spec/compose-go/v2 v2.4.4 9 | github.com/joho/godotenv v1.5.1 10 | ) 11 | 12 | // Test 13 | require github.com/google/go-cmp v0.6.0 14 | 15 | require ( 16 | dario.cat/mergo v1.0.1 // indirect 17 | github.com/Masterminds/goutils v1.1.1 // indirect 18 | github.com/Masterminds/semver/v3 v3.3.0 // indirect 19 | github.com/distribution/reference v0.6.0 // indirect 20 | github.com/docker/go-connections v0.5.0 // indirect 21 | github.com/docker/go-units v0.5.0 // indirect 22 | github.com/go-viper/mapstructure/v2 v2.0.0 // indirect 23 | github.com/google/uuid v1.6.0 // indirect 24 | github.com/huandu/xstrings v1.5.0 // indirect 25 | github.com/mattn/go-shellwords v1.0.12 // indirect 26 | github.com/mitchellh/copystructure v1.2.0 // indirect 27 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 28 | github.com/opencontainers/go-digest v1.0.0 // indirect 29 | github.com/shopspring/decimal v1.4.0 // indirect 30 | github.com/sirupsen/logrus v1.9.3 // indirect 31 | github.com/spf13/cast v1.7.0 // indirect 32 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 33 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 34 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 35 | golang.org/x/crypto v0.28.0 // indirect 36 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect 37 | golang.org/x/sync v0.7.0 // indirect 38 | golang.org/x/sys v0.26.0 // indirect 39 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 40 | gopkg.in/yaml.v3 v3.0.1 // indirect 41 | ) 42 | 43 | // LINT.ThenChange(flake.nix) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= 2 | dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 3 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 4 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 5 | github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= 6 | github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 7 | github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= 8 | github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= 9 | github.com/compose-spec/compose-go/v2 v2.4.4 h1:cvHBl5Jf1iNBmRrZCICmHvaoskYc1etTPEMLKVwokAY= 10 | github.com/compose-spec/compose-go/v2 v2.4.4/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 15 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 16 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 17 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 18 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 19 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 20 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 21 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 22 | github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= 23 | github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 24 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 25 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 26 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 27 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 28 | github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= 29 | github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 30 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 31 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 32 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 33 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 34 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 35 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 36 | github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= 37 | github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 38 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 39 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 40 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 41 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 42 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 43 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 44 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 47 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 48 | github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= 49 | github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= 50 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 51 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 52 | github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= 53 | github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 54 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 55 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 56 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 58 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 59 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 60 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 61 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 62 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 63 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 64 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 65 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 66 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 67 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 68 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= 69 | golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= 70 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 71 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 72 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 73 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 74 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 75 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 76 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 77 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 78 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 80 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 81 | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= 82 | gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= 83 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "slices" 11 | "strings" 12 | 13 | "github.com/joho/godotenv" 14 | ) 15 | 16 | // mapToKeyValArray converts a map into a _sorted_ list of KEY=VAL entries. 17 | func mapToKeyValArray(m map[string]string) []string { 18 | var arr []string 19 | for k, v := range m { 20 | arr = append(arr, fmt.Sprintf("%s=%s", k, v)) 21 | } 22 | slices.Sort(arr) 23 | return arr 24 | } 25 | 26 | func mapToRepeatedKeyValFlag(flagName string, m map[string]string) []string { 27 | arr := mapToKeyValArray(m) 28 | for i, v := range arr { 29 | arr[i] = fmt.Sprintf("%s=%s", flagName, v) 30 | } 31 | return arr 32 | } 33 | 34 | func sliceToStringArray(s []string) string { 35 | b := strings.Builder{} 36 | b.WriteString("[") 37 | for i := range s { 38 | // We purposefully do not use %q to avoid Go's built-in string escaping. 39 | // Otherwise, we'd escape " characters a 2nd time for Nix. 40 | s[i] = fmt.Sprintf(`"%s"`, s[i]) 41 | } 42 | return fmt.Sprintf("[%s]", strings.Join(s, ", ")) 43 | } 44 | 45 | // ReadEnvFiles reads the given set of env files into a list of KEY=VAL entries. 46 | // 47 | // If mergeWithEnv is set, the running env is merged with the provided env files. Any 48 | // duplicate variables will be overridden by the running env. 49 | // 50 | // If ignoreMissing is set, any missing env files will be ignored. This is useful for cases 51 | // where an env file is not available during conversion to Nix. 52 | func ReadEnvFiles(envFiles []string, mergeWithEnv, ignoreMissing bool) (env []string, _ error) { 53 | for _, p := range envFiles { 54 | if strings.TrimSpace(p) == "" { 55 | continue 56 | } 57 | envMap, err := godotenv.Read(p) 58 | if err != nil { 59 | if ignoreMissing && errors.Is(err, os.ErrNotExist) { 60 | log.Printf("Ignoring missing env file %q...", p) 61 | continue 62 | } 63 | return nil, fmt.Errorf("failed to parse env file %q: %w", p, err) 64 | } 65 | for k, v := range envMap { 66 | env = append(env, fmt.Sprintf("%s=%s", k, v)) 67 | } 68 | } 69 | 70 | if mergeWithEnv { 71 | env = append(env, os.Environ()...) 72 | } 73 | 74 | return env, nil 75 | } 76 | 77 | // formatNixCode will format Nix code by calling 'nixfmt' and passing in the 78 | // given code via stdin. 79 | func formatNixCode(contents []byte) ([]byte, error) { 80 | // Check for existence of 'nixfmt' in $PATH. 81 | nixfmtPath, err := exec.LookPath("nixfmt") 82 | if err != nil { 83 | return nil, fmt.Errorf("'nixfmt' not found in $PATH: %w", err) 84 | } 85 | 86 | cmd := exec.Command(nixfmtPath) 87 | cmd.Stdin = bytes.NewBuffer(contents) 88 | 89 | // Overwrite contents with formatted output. 90 | contents, err = cmd.Output() 91 | if err != nil { 92 | return nil, fmt.Errorf("failed to run 'nixfmt' on contents: %w", err) 93 | } 94 | 95 | return contents, nil 96 | } 97 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path" 10 | "regexp" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | const ( 16 | // LINT.OnChange(version) 17 | appVersion = "0.3.2-pre" 18 | // LINT.ThenChange(flake.nix:version) 19 | ) 20 | 21 | // TODO(aksiksi): Investigate parsing flags into structs using the *Val functions. 22 | var inputs = flag.String("inputs", "docker-compose.yml", "one or more comma-separated path(s) to Compose file(s).") 23 | var output = flag.String("output", "docker-compose.nix", "path to output Nix file.") 24 | var project = flag.String("project", "", "project name used as a prefix for generated resources. this overrides any top-level \"name\" set in the Compose file(s).") 25 | var serviceInclude = flag.String("service_include", "", "regex pattern for services to include.") 26 | var envFiles = flag.String("env_files", "", "one or more comma-separated paths to .env file(s).") 27 | var rootPath = flag.String("root_path", "", "absolute path to use as the root for any relative paths in the Compose file (e.g., volumes, env files). defaults to the current working directory.") 28 | var includeEnvFiles = flag.Bool("include_env_files", false, "include env files in the NixOS container definition.") 29 | var envFilesOnly = flag.Bool("env_files_only", false, "only use env file(s) in the NixOS container definitions.") 30 | var ignoreMissingEnvFiles = flag.Bool("ignore_missing_env_files", false, "if set, missing env files will be ignored.") 31 | var autoStart = flag.Bool("auto_start", true, "auto-start setting for generated service(s). this applies to all services, not just containers.") 32 | var runtime = flag.String("runtime", "podman", `one of: ["podman", "docker"].`) 33 | var useComposeLogDriver = flag.Bool("use_compose_log_driver", false, "if set, always use the Docker Compose log driver.") 34 | var generateUnusedResources = flag.Bool("generate_unused_resources", false, "if set, unused resources (e.g., networks) will be generated even if no containers use them.") 35 | var checkSystemdMounts = flag.Bool("check_systemd_mounts", false, "if set, volume paths will be checked against systemd mount paths on the current machine and marked as container dependencies.") 36 | var checkBindMounts = flag.Bool("check_bind_mounts", false, "if set, check that bind mount paths exist. this is useful if running the generated Nix code on the same machine.") 37 | var useUpheldBy = flag.Bool("use_upheld_by", false, "if set, upheldBy will be used for service dependencies (NixOS 24.05+).") 38 | var removeVolumes = flag.Bool("remove_volumes", false, "if set, volumes will be removed on systemd service stop.") 39 | var createRootTarget = flag.Bool("create_root_target", true, "if set, a root systemd target will be created, which when stopped tears down all resources.") 40 | var defaultStopTimeout = flag.Duration("default_stop_timeout", defaultSystemdStopTimeout, "default stop timeout for generated container services.") 41 | var build = flag.Bool("build", false, "if set, generated container build systemd services will be enabled.") 42 | var writeNixSetup = flag.Bool("write_nix_setup", true, "if true, Nix setup code is written to output (runtime, DNS, autoprune, etc.)") 43 | var autoFormat = flag.Bool("auto_format", false, `if true, Nix output will be formatted using "nixfmt" (must be present in $PATH).`) 44 | var optionPrefix = flag.String("option_prefix", "", "Prefix for the option. If empty, the project name will be used as the option name. (e.g. custom.containers)") 45 | var enableOption = flag.Bool("enable_option", false, "generate a NixOS module option. this allows you to enable or disable the generated module from within your NixOS config. by default, the option will be named \"options.[project_name]\", but you can add a prefix using the \"option_prefix\" flag.") 46 | var version = flag.Bool("version", false, "display version and exit") 47 | 48 | type OsGetWd struct{} 49 | 50 | func (*OsGetWd) GetWd() (string, error) { 51 | return os.Getwd() 52 | } 53 | 54 | func main() { 55 | flag.Parse() 56 | 57 | if *version { 58 | fmt.Printf("compose2nix v%s\n", appVersion) 59 | return 60 | } 61 | if *output == "" { 62 | log.Fatal("No output path specified.") 63 | } 64 | 65 | ctx := context.Background() 66 | 67 | if strings.TrimSpace(*inputs) == "" { 68 | log.Fatalf("One or more paths must be specified") 69 | } 70 | 71 | inputs := strings.Split(*inputs, ",") 72 | 73 | var envFilesList []string 74 | if *envFiles != "" { 75 | envFilesList = strings.Split(*envFiles, ",") 76 | } 77 | 78 | var containerRuntime ContainerRuntime 79 | if *runtime == "podman" { 80 | containerRuntime = ContainerRuntimePodman 81 | } else if *runtime == "docker" { 82 | containerRuntime = ContainerRuntimeDocker 83 | } else { 84 | log.Fatalf("Invalid --runtime: %q", *runtime) 85 | } 86 | 87 | var serviceIncludeRegexp *regexp.Regexp 88 | if *serviceInclude != "" { 89 | pat, err := regexp.Compile(*serviceInclude) 90 | if err != nil { 91 | log.Fatalf("Failed to parse -service_include pattern %q: %v", *serviceInclude, err) 92 | } 93 | serviceIncludeRegexp = pat 94 | } 95 | 96 | start := time.Now() 97 | g := Generator{ 98 | Project: NewProject(*project), 99 | Runtime: containerRuntime, 100 | Inputs: inputs, 101 | EnvFiles: envFilesList, 102 | RootPath: *rootPath, 103 | IncludeEnvFiles: *includeEnvFiles, 104 | EnvFilesOnly: *envFilesOnly, 105 | IgnoreMissingEnvFiles: *ignoreMissingEnvFiles, 106 | ServiceInclude: serviceIncludeRegexp, 107 | AutoStart: *autoStart, 108 | UseComposeLogDriver: *useComposeLogDriver, 109 | GenerateUnusedResources: *generateUnusedResources, 110 | CheckSystemdMounts: *checkSystemdMounts, 111 | CheckBindMounts: *checkBindMounts, 112 | UseUpheldBy: *useUpheldBy, 113 | RemoveVolumes: *removeVolumes, 114 | NoCreateRootTarget: !*createRootTarget, 115 | WriteHeader: true, 116 | NoWriteNixSetup: !*writeNixSetup, 117 | AutoFormat: *autoFormat, 118 | DefaultStopTimeout: *defaultStopTimeout, 119 | IncludeBuild: *build, 120 | GetWorkingDir: &OsGetWd{}, 121 | OptionPrefix: *optionPrefix, 122 | EnableOption: *enableOption, 123 | } 124 | containerConfig, err := g.Run(ctx) 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | fmt.Printf("Generated NixOS config in %v\n", time.Since(start)) 129 | 130 | dir := path.Dir(*output) 131 | if _, err := os.Stat(dir); err != nil { 132 | log.Fatalf("Directory %q does not exist: %v", dir, err) 133 | } 134 | f, err := os.Create(*output) 135 | if err != nil { 136 | log.Fatalf("Failed to create file %q: %v", *output, err) 137 | } 138 | if err := containerConfig.Write(f); err != nil { 139 | log.Fatal(err) 140 | } 141 | fmt.Printf("Wrote NixOS config to %s\n", *output) 142 | } 143 | -------------------------------------------------------------------------------- /nix.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/template" 8 | ) 9 | 10 | // Compose V2 uses "-" for container names: https://docs.docker.com/compose/migrate/#service-container-names 11 | // 12 | // Volumes and networks still use "_", but we'll ignore that here: https://github.com/docker/compose/issues/9618 13 | const DefaultProjectSeparator = "-" 14 | 15 | type ContainerRuntime int 16 | 17 | const ( 18 | ContainerRuntimeInvalid ContainerRuntime = iota 19 | ContainerRuntimeDocker 20 | ContainerRuntimePodman 21 | ) 22 | 23 | func (c ContainerRuntime) String() string { 24 | switch c { 25 | case ContainerRuntimeDocker: 26 | return "docker" 27 | case ContainerRuntimePodman: 28 | return "podman" 29 | case ContainerRuntimeInvalid: 30 | return "invalid-container-runtime" 31 | default: 32 | panic("Unreachable") 33 | } 34 | } 35 | 36 | type Project struct { 37 | Name string 38 | separator string 39 | } 40 | 41 | func NewProject(name string) *Project { 42 | if name == "" { 43 | return nil 44 | } 45 | return &Project{Name: name, separator: DefaultProjectSeparator} 46 | } 47 | 48 | func (p *Project) With(name string) string { 49 | if p == nil { 50 | return name 51 | } 52 | return fmt.Sprintf("%s%s%s", p.Name, p.separator, name) 53 | } 54 | 55 | type IpamConfig struct { 56 | Subnet string 57 | IPRange string 58 | Gateway string 59 | AuxAddresses []string 60 | } 61 | 62 | type NixNetwork struct { 63 | Runtime ContainerRuntime 64 | Name string 65 | OriginalName string 66 | Driver string 67 | DriverOpts map[string]string 68 | External bool 69 | Labels map[string]string 70 | IpamDriver string 71 | IpamConfigs []IpamConfig 72 | ExtraOptions []string 73 | } 74 | 75 | func (n *NixNetwork) Unit() string { 76 | return fmt.Sprintf("%s-network-%s.service", n.Runtime, n.Name) 77 | } 78 | 79 | func (n *NixNetwork) Command() string { 80 | cmd := fmt.Sprintf("%[1]s network inspect %[2]s || %[1]s network create %[2]s", n.Runtime, n.Name) 81 | if n.Driver != "" { 82 | cmd += fmt.Sprintf(" --driver=%s", n.Driver) 83 | } 84 | if len(n.DriverOpts) > 0 { 85 | driverOpts := mapToRepeatedKeyValFlag("--opt", n.DriverOpts) 86 | cmd += " " + strings.Join(driverOpts, " ") 87 | } 88 | 89 | if n.IpamDriver != "" { 90 | cmd += fmt.Sprintf(" --ipam-driver=%s", n.IpamDriver) 91 | } 92 | for _, cfg := range n.IpamConfigs { 93 | if cfg.Subnet != "" { 94 | cmd += fmt.Sprintf(" --subnet=%s", cfg.Subnet) 95 | } 96 | if cfg.IPRange != "" { 97 | cmd += fmt.Sprintf(" --ip-range=%s", cfg.IPRange) 98 | } 99 | if cfg.Gateway != "" { 100 | cmd += fmt.Sprintf(" --gateway=%s", cfg.Gateway) 101 | } 102 | for _, addr := range cfg.AuxAddresses { 103 | cmd += fmt.Sprintf(` --aux-address="%s"`, addr) 104 | } 105 | } 106 | 107 | if len(n.ExtraOptions) > 0 { 108 | cmd += " " + strings.Join(n.ExtraOptions, " ") 109 | } 110 | 111 | if len(n.Labels) > 0 { 112 | labels := mapToRepeatedKeyValFlag("--label", n.Labels) 113 | cmd += " " + strings.Join(labels, " ") 114 | } 115 | return cmd 116 | } 117 | 118 | type NixVolume struct { 119 | Runtime ContainerRuntime 120 | Name string 121 | Driver string 122 | DriverOpts map[string]string 123 | External bool 124 | Labels map[string]string 125 | RemoveOnStop bool 126 | RequiresMountsFor []string 127 | } 128 | 129 | func (v *NixVolume) Path() string { 130 | return v.DriverOpts["device"] 131 | } 132 | 133 | func (v *NixVolume) Unit() string { 134 | return fmt.Sprintf("%s-volume-%s.service", v.Runtime, v.Name) 135 | } 136 | 137 | func (v *NixVolume) Command() string { 138 | cmd := fmt.Sprintf("%[1]s volume inspect %[2]s || %[1]s volume create %[2]s", v.Runtime, v.Name) 139 | if v.Driver != "" { 140 | cmd += fmt.Sprintf(" --driver=%s", v.Driver) 141 | } 142 | if len(v.DriverOpts) > 0 { 143 | driverOpts := mapToRepeatedKeyValFlag("--opt", v.DriverOpts) 144 | cmd += " " + strings.Join(driverOpts, " ") 145 | } 146 | if len(v.Labels) > 0 { 147 | labels := mapToRepeatedKeyValFlag("--label", v.Labels) 148 | cmd += " " + strings.Join(labels, " ") 149 | } 150 | return cmd 151 | } 152 | 153 | // NixContainerSystemdConfig configures the container's systemd config. 154 | // In particular, this allows control of the container restart policy through systemd 155 | // service and unit configs. 156 | // 157 | // Each key-value pair in a map represents a systemd key and its value (e.g., Restart=always). 158 | // Users can provide custom config keys by setting the compose2nix.systemd.* label on the service. 159 | type NixContainerSystemdConfig struct { 160 | Service ServiceConfig 161 | Unit UnitConfig 162 | // NixOS treats these differently, probably to fix the rename issue in 163 | // earlier systemd versions. 164 | // See: https://unix.stackexchange.com/a/464098 165 | StartLimitBurst *int 166 | } 167 | 168 | func NewNixContainerSystemdConfig() *NixContainerSystemdConfig { 169 | return &NixContainerSystemdConfig{ 170 | Service: ServiceConfig{}, 171 | Unit: UnitConfig{}, 172 | } 173 | } 174 | 175 | // https://search.nixos.org/options?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=oci-container 176 | type NixContainer struct { 177 | Runtime ContainerRuntime 178 | Name string 179 | Image string 180 | Environment map[string]string 181 | EnvFiles []string 182 | Volumes map[string]string 183 | Ports []string 184 | Labels map[string]string 185 | Networks []string 186 | DependsOn []string 187 | LogDriver string 188 | ExtraOptions []string 189 | SystemdConfig *NixContainerSystemdConfig 190 | User string 191 | Command []string 192 | AutoStart bool 193 | } 194 | 195 | func (c *NixContainer) Unit() string { 196 | return fmt.Sprintf("%s-%s.service", c.Runtime, c.Name) 197 | } 198 | 199 | // https://docs.docker.com/reference/compose-file/services/#pull_policy 200 | // https://docs.podman.io/en/latest/markdown/podman-build.1.html#pull-policy 201 | type ServicePullPolicy int 202 | 203 | const ( 204 | ServicePullPolicyInvalid ServicePullPolicy = iota 205 | ServicePullPolicyAlways 206 | ServicePullPolicyNever 207 | ServicePullPolicyMissing 208 | ServicePullPolicyBuild 209 | ServicePullPolicyUnset 210 | ) 211 | 212 | func NewServicePullPolicy(s string) ServicePullPolicy { 213 | switch strings.TrimSpace(s) { 214 | case "always": 215 | return ServicePullPolicyAlways 216 | case "never": 217 | return ServicePullPolicyNever 218 | case "missing", "if_not_present": 219 | return ServicePullPolicyMissing 220 | case "build": 221 | return ServicePullPolicyBuild 222 | default: 223 | return ServicePullPolicyUnset 224 | } 225 | } 226 | 227 | // https://docs.docker.com/reference/compose-file/build/ 228 | // https://docs.docker.com/reference/cli/docker/buildx/build/ 229 | type NixBuild struct { 230 | Runtime ContainerRuntime 231 | Context string 232 | PullPolicy ServicePullPolicy 233 | IsGitRepo bool 234 | Args map[string]*string 235 | Tags []string 236 | Dockerfile string // Relative to context path. 237 | ContainerName string // Name of the resolved Nix container. 238 | } 239 | 240 | func (b *NixBuild) UnitName() string { 241 | return fmt.Sprintf("%s-build-%s", b.Runtime, b.ContainerName) 242 | } 243 | 244 | func (b *NixBuild) Unit() string { 245 | return b.UnitName() + ".service" 246 | } 247 | 248 | func (b *NixBuild) Command() string { 249 | cmd := fmt.Sprintf("%s build", b.Runtime) 250 | 251 | for _, tag := range b.Tags { 252 | cmd += fmt.Sprintf(" -t %s", tag) 253 | } 254 | for name, arg := range b.Args { 255 | if arg != nil { 256 | cmd += fmt.Sprintf(" --build-arg %s=%s", name, *arg) 257 | } else { 258 | cmd += fmt.Sprintf(" --build-arg %s", name) 259 | } 260 | } 261 | if b.Dockerfile != "" && b.Dockerfile != "Dockerfile" { 262 | cmd += fmt.Sprintf(" -f %s", b.Dockerfile) 263 | } 264 | 265 | if b.IsGitRepo { 266 | cmd += " " + b.Context 267 | } else { 268 | cmd += " ." 269 | } 270 | 271 | return cmd 272 | } 273 | 274 | type NixContainerConfig struct { 275 | Version string 276 | Project *Project 277 | Runtime ContainerRuntime 278 | Containers []*NixContainer 279 | Builds []*NixBuild 280 | Networks []*NixNetwork 281 | Volumes []*NixVolume 282 | CreateRootTarget bool 283 | WriteNixSetup bool 284 | AutoFormat bool 285 | AutoStart bool 286 | IncludeBuild bool 287 | Option string 288 | EnableOption bool 289 | } 290 | 291 | func (c *NixContainerConfig) String() string { 292 | s := strings.Builder{} 293 | internalFuncMap := template.FuncMap{ 294 | "cfg": c.configTemplateFunc, 295 | "execTemplate": execTemplate(nixTemplates), 296 | "indentNonEmpty": indentNonEmpty, 297 | "rootTarget": c.rootTargetTemplateFunc, 298 | } 299 | nixTemplates := template.Must(nixTemplates.Funcs(internalFuncMap).ParseFS(templateFS, "templates/*.tmpl")) 300 | if err := nixTemplates.ExecuteTemplate(&s, "main.nix.tmpl", c); err != nil { 301 | // This should never be hit under normal operation. 302 | panic(err) 303 | } 304 | return s.String() 305 | } 306 | 307 | // Write writes out the Nix config to the provided Writer. 308 | // 309 | // If the AutoFormat option on this struct is set to "true", this method will 310 | // attempt to format the Nix config by calling "nixfmt" and passing in the 311 | // fully built config via stdin. 312 | func (c *NixContainerConfig) Write(out io.Writer) error { 313 | config := []byte(c.String()) 314 | 315 | if c.AutoFormat { 316 | formatted, err := formatNixCode(config) 317 | if err != nil { 318 | return err 319 | } 320 | config = formatted 321 | } 322 | 323 | if _, err := out.Write(config); err != nil { 324 | return fmt.Errorf("failed to write Nix code: %w", err) 325 | } 326 | 327 | return nil 328 | } 329 | 330 | func rootTarget(runtime ContainerRuntime, project *Project) string { 331 | return fmt.Sprintf("%s-compose-%s", runtime, project.With("root")) 332 | } 333 | 334 | func (c *NixContainerConfig) rootTargetTemplateFunc() string { 335 | if !c.CreateRootTarget { 336 | return "" 337 | } 338 | return rootTarget(c.Runtime, c.Project) 339 | } 340 | 341 | func (c *NixContainerConfig) configTemplateFunc() *NixContainerConfig { 342 | return c 343 | } 344 | -------------------------------------------------------------------------------- /nixos-test/compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | service-a: 4 | image: docker.io/library/nginx:stable-alpine-slim 5 | environment: 6 | TZ: ${TIMEZONE} 7 | test.key: ABC 8 | env_file: 9 | - path: /tmp/test.env 10 | required: false 11 | volumes: 12 | - /var/volumes/service-a:/config 13 | - storage:/storage 14 | deploy: 15 | resources: 16 | limits: 17 | cpus: "0.5" 18 | labels: 19 | - 'compose2nix.systemd.service.Restart=no' 20 | - "compose2nix.systemd.service.RuntimeMaxSec=360" 21 | - "compose2nix.systemd.unit.Description=This is the service-a container!" 22 | - "escape-me=\"hello\"" 23 | restart: unless-stopped 24 | service-b: 25 | image: docker.io/library/nginx:stable-alpine-slim 26 | container_name: service-b 27 | environment: 28 | TZ: ${TIMEZONE} 29 | volumes: 30 | - /var/volumes/service-b:/config 31 | - storage:/storage 32 | - books:/books 33 | labels: 34 | - "compose2nix.systemd.unit.AllowIsolate=no" 35 | - "compose2nix.systemd.service.RuntimeMaxSec=360" 36 | depends_on: 37 | - service-a 38 | healthcheck: 39 | test: echo abc && true 40 | networks: 41 | something: 42 | ipv4_address: 192.168.8.20 43 | restart: on-failure:3 44 | no-restart: 45 | image: docker.io/library/nginx:stable-alpine-slim 46 | entrypoint: 47 | image: docker.io/library/nginx:stable-alpine-slim 48 | entrypoint: 49 | - echo 50 | - abc 51 | 52 | networks: 53 | something: 54 | ipam: 55 | config: 56 | - subnet: 192.168.8.0/24 57 | gateway: 192.168.8.1 58 | labels: 59 | - "test-label=okay" 60 | - "escape-me=''hello''" 61 | another: 62 | driver: bridge 63 | enable_ipv6: true 64 | 65 | volumes: 66 | storage: 67 | name: storage 68 | driver_opts: 69 | type: none 70 | device: /mnt/media 71 | o: bind 72 | labels: 73 | - "escape-me=''hello''" 74 | books: 75 | driver_opts: 76 | type: none 77 | device: /mnt/media/Books 78 | o: bind 79 | 80 | -------------------------------------------------------------------------------- /nixos-test/docker-compose.nix: -------------------------------------------------------------------------------- 1 | # Auto-generated using compose2nix v0.3.2-pre. 2 | { pkgs, lib, config, ... }: 3 | 4 | { 5 | options.custom.prefix.myproject = { 6 | enable = lib.mkEnableOption "Enable myproject"; 7 | }; 8 | 9 | config = lib.mkIf config.custom.prefix.myproject.enable { 10 | # Runtime 11 | virtualisation.docker = { 12 | enable = true; 13 | autoPrune.enable = true; 14 | }; 15 | virtualisation.oci-containers.backend = "docker"; 16 | 17 | # Containers 18 | virtualisation.oci-containers.containers."myproject-entrypoint" = { 19 | image = "docker.io/library/nginx:stable-alpine-slim"; 20 | log-driver = "journald"; 21 | extraOptions = [ 22 | "--entrypoint=[\"echo\", \"abc\"]" 23 | "--network-alias=entrypoint" 24 | "--network=myproject_default" 25 | ]; 26 | }; 27 | systemd.services."docker-myproject-entrypoint" = { 28 | serviceConfig = { 29 | Restart = lib.mkOverride 90 "no"; 30 | }; 31 | after = [ 32 | "docker-network-myproject_default.service" 33 | ]; 34 | requires = [ 35 | "docker-network-myproject_default.service" 36 | ]; 37 | partOf = [ 38 | "docker-compose-myproject-root.target" 39 | ]; 40 | upheldBy = [ 41 | "docker-network-myproject_default.service" 42 | ]; 43 | wantedBy = [ 44 | "docker-compose-myproject-root.target" 45 | ]; 46 | }; 47 | virtualisation.oci-containers.containers."myproject-no-restart" = { 48 | image = "docker.io/library/nginx:stable-alpine-slim"; 49 | log-driver = "journald"; 50 | extraOptions = [ 51 | "--network-alias=no-restart" 52 | "--network=myproject_default" 53 | ]; 54 | }; 55 | systemd.services."docker-myproject-no-restart" = { 56 | serviceConfig = { 57 | Restart = lib.mkOverride 90 "no"; 58 | }; 59 | after = [ 60 | "docker-network-myproject_default.service" 61 | ]; 62 | requires = [ 63 | "docker-network-myproject_default.service" 64 | ]; 65 | partOf = [ 66 | "docker-compose-myproject-root.target" 67 | ]; 68 | upheldBy = [ 69 | "docker-network-myproject_default.service" 70 | ]; 71 | wantedBy = [ 72 | "docker-compose-myproject-root.target" 73 | ]; 74 | }; 75 | virtualisation.oci-containers.containers."myproject-service-a" = { 76 | image = "docker.io/library/nginx:stable-alpine-slim"; 77 | environment = { 78 | "TZ" = "America/New_York"; 79 | "test.key" = "ABC"; 80 | }; 81 | environmentFiles = [ 82 | "/tmp/test.env" 83 | ]; 84 | volumes = [ 85 | "/var/volumes/service-a:/config:rw" 86 | "storage:/storage:rw" 87 | ]; 88 | labels = { 89 | "compose2nix.systemd.service.Restart" = "no"; 90 | "compose2nix.systemd.service.RuntimeMaxSec" = "360"; 91 | "compose2nix.systemd.unit.Description" = "This is the service-a container!"; 92 | "escape-me" = "\"hello\""; 93 | }; 94 | log-driver = "journald"; 95 | extraOptions = [ 96 | "--cpus=0.5" 97 | "--network-alias=service-a" 98 | "--network=myproject_default" 99 | ]; 100 | }; 101 | systemd.services."docker-myproject-service-a" = { 102 | serviceConfig = { 103 | Restart = lib.mkOverride 90 "no"; 104 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 105 | RestartSec = lib.mkOverride 90 "100ms"; 106 | RestartSteps = lib.mkOverride 90 9; 107 | RuntimeMaxSec = lib.mkOverride 90 360; 108 | }; 109 | unitConfig = { 110 | Description = lib.mkOverride 90 "This is the service-a container!"; 111 | }; 112 | after = [ 113 | "docker-network-myproject_default.service" 114 | "docker-volume-storage.service" 115 | ]; 116 | requires = [ 117 | "docker-network-myproject_default.service" 118 | "docker-volume-storage.service" 119 | ]; 120 | partOf = [ 121 | "docker-compose-myproject-root.target" 122 | ]; 123 | upheldBy = [ 124 | "docker-network-myproject_default.service" 125 | "docker-volume-storage.service" 126 | ]; 127 | wantedBy = [ 128 | "docker-compose-myproject-root.target" 129 | ]; 130 | unitConfig.RequiresMountsFor = [ 131 | "/var/volumes/service-a" 132 | ]; 133 | }; 134 | virtualisation.oci-containers.containers."service-b" = { 135 | image = "docker.io/library/nginx:stable-alpine-slim"; 136 | environment = { 137 | "TZ" = "America/New_York"; 138 | }; 139 | volumes = [ 140 | "/var/volumes/service-b:/config:rw" 141 | "myproject_books:/books:rw" 142 | "storage:/storage:rw" 143 | ]; 144 | labels = { 145 | "compose2nix.systemd.service.RuntimeMaxSec" = "360"; 146 | "compose2nix.systemd.unit.AllowIsolate" = "no"; 147 | }; 148 | dependsOn = [ 149 | "myproject-service-a" 150 | ]; 151 | log-driver = "journald"; 152 | extraOptions = [ 153 | "--health-cmd=echo abc && true" 154 | "--ip=192.168.8.20" 155 | "--network-alias=service-b" 156 | "--network=myproject_something" 157 | ]; 158 | }; 159 | systemd.services."docker-service-b" = { 160 | serviceConfig = { 161 | Restart = lib.mkOverride 90 "on-failure"; 162 | RuntimeMaxSec = lib.mkOverride 90 360; 163 | }; 164 | startLimitBurst = 3; 165 | unitConfig = { 166 | AllowIsolate = lib.mkOverride 90 "no"; 167 | StartLimitIntervalSec = lib.mkOverride 90 "infinity"; 168 | }; 169 | after = [ 170 | "docker-network-myproject_something.service" 171 | "docker-volume-myproject_books.service" 172 | "docker-volume-storage.service" 173 | ]; 174 | requires = [ 175 | "docker-network-myproject_something.service" 176 | "docker-volume-myproject_books.service" 177 | "docker-volume-storage.service" 178 | ]; 179 | partOf = [ 180 | "docker-compose-myproject-root.target" 181 | ]; 182 | upheldBy = [ 183 | "docker-myproject-service-a.service" 184 | "docker-network-myproject_something.service" 185 | "docker-volume-myproject_books.service" 186 | "docker-volume-storage.service" 187 | ]; 188 | wantedBy = [ 189 | "docker-compose-myproject-root.target" 190 | ]; 191 | unitConfig.RequiresMountsFor = [ 192 | "/var/volumes/service-b" 193 | ]; 194 | }; 195 | 196 | # Networks 197 | systemd.services."docker-network-myproject_another" = { 198 | path = [ pkgs.docker ]; 199 | serviceConfig = { 200 | Type = "oneshot"; 201 | RemainAfterExit = true; 202 | ExecStop = "docker network rm -f myproject_another"; 203 | }; 204 | script = '' 205 | docker network inspect myproject_another || docker network create myproject_another --driver=bridge --ipv6 206 | ''; 207 | partOf = [ "docker-compose-myproject-root.target" ]; 208 | wantedBy = [ "docker-compose-myproject-root.target" ]; 209 | }; 210 | systemd.services."docker-network-myproject_default" = { 211 | path = [ pkgs.docker ]; 212 | serviceConfig = { 213 | Type = "oneshot"; 214 | RemainAfterExit = true; 215 | ExecStop = "docker network rm -f myproject_default"; 216 | }; 217 | script = '' 218 | docker network inspect myproject_default || docker network create myproject_default 219 | ''; 220 | partOf = [ "docker-compose-myproject-root.target" ]; 221 | wantedBy = [ "docker-compose-myproject-root.target" ]; 222 | }; 223 | systemd.services."docker-network-myproject_something" = { 224 | path = [ pkgs.docker ]; 225 | serviceConfig = { 226 | Type = "oneshot"; 227 | RemainAfterExit = true; 228 | ExecStop = "docker network rm -f myproject_something"; 229 | }; 230 | script = '' 231 | docker network inspect myproject_something || docker network create myproject_something --subnet=192.168.8.0/24 --gateway=192.168.8.1 --label=escape-me='''hello''' --label=test-label=okay 232 | ''; 233 | partOf = [ "docker-compose-myproject-root.target" ]; 234 | wantedBy = [ "docker-compose-myproject-root.target" ]; 235 | }; 236 | 237 | # Volumes 238 | systemd.services."docker-volume-myproject_books" = { 239 | path = [ pkgs.docker ]; 240 | serviceConfig = { 241 | Type = "oneshot"; 242 | RemainAfterExit = true; 243 | }; 244 | unitConfig.RequiresMountsFor = [ 245 | "/mnt/media/Books" 246 | ]; 247 | script = '' 248 | docker volume inspect myproject_books || docker volume create myproject_books --opt=device=/mnt/media/Books --opt=o=bind --opt=type=none 249 | ''; 250 | partOf = [ "docker-compose-myproject-root.target" ]; 251 | wantedBy = [ "docker-compose-myproject-root.target" ]; 252 | }; 253 | systemd.services."docker-volume-storage" = { 254 | path = [ pkgs.docker ]; 255 | serviceConfig = { 256 | Type = "oneshot"; 257 | RemainAfterExit = true; 258 | }; 259 | unitConfig.RequiresMountsFor = [ 260 | "/mnt/media" 261 | ]; 262 | script = '' 263 | docker volume inspect storage || docker volume create storage --opt=device=/mnt/media --opt=o=bind --opt=type=none --label=escape-me='''hello''' 264 | ''; 265 | partOf = [ "docker-compose-myproject-root.target" ]; 266 | wantedBy = [ "docker-compose-myproject-root.target" ]; 267 | }; 268 | 269 | # Root service 270 | # When started, this will automatically create all resources and start 271 | # the containers. When stopped, this will teardown all resources. 272 | systemd.targets."docker-compose-myproject-root" = { 273 | unitConfig = { 274 | Description = "Root target generated by compose2nix."; 275 | }; 276 | wantedBy = [ "multi-user.target" ]; 277 | }; 278 | }; 279 | } 280 | -------------------------------------------------------------------------------- /nixos-test/podman-compose.nix: -------------------------------------------------------------------------------- 1 | # Auto-generated using compose2nix v0.3.2-pre. 2 | { pkgs, lib, config, ... }: 3 | 4 | { 5 | # Runtime 6 | virtualisation.podman = { 7 | enable = true; 8 | autoPrune.enable = true; 9 | dockerCompat = true; 10 | }; 11 | 12 | # Enable container name DNS for all Podman networks. 13 | networking.firewall.interfaces = let 14 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 15 | in { 16 | "${matchAll}".allowedUDPPorts = [ 53 ]; 17 | }; 18 | 19 | virtualisation.oci-containers.backend = "podman"; 20 | 21 | # Containers 22 | virtualisation.oci-containers.containers."myproject-entrypoint" = { 23 | image = "docker.io/library/nginx:stable-alpine-slim"; 24 | log-driver = "journald"; 25 | extraOptions = [ 26 | "--entrypoint=[\"echo\", \"abc\"]" 27 | "--network-alias=entrypoint" 28 | "--network=myproject_default" 29 | ]; 30 | }; 31 | systemd.services."podman-myproject-entrypoint" = { 32 | serviceConfig = { 33 | Restart = lib.mkOverride 90 "no"; 34 | }; 35 | after = [ 36 | "podman-network-myproject_default.service" 37 | ]; 38 | requires = [ 39 | "podman-network-myproject_default.service" 40 | ]; 41 | partOf = [ 42 | "podman-compose-myproject-root.target" 43 | ]; 44 | upheldBy = [ 45 | "podman-network-myproject_default.service" 46 | ]; 47 | wantedBy = [ 48 | "podman-compose-myproject-root.target" 49 | ]; 50 | }; 51 | virtualisation.oci-containers.containers."myproject-no-restart" = { 52 | image = "docker.io/library/nginx:stable-alpine-slim"; 53 | log-driver = "journald"; 54 | extraOptions = [ 55 | "--network-alias=no-restart" 56 | "--network=myproject_default" 57 | ]; 58 | }; 59 | systemd.services."podman-myproject-no-restart" = { 60 | serviceConfig = { 61 | Restart = lib.mkOverride 90 "no"; 62 | }; 63 | after = [ 64 | "podman-network-myproject_default.service" 65 | ]; 66 | requires = [ 67 | "podman-network-myproject_default.service" 68 | ]; 69 | partOf = [ 70 | "podman-compose-myproject-root.target" 71 | ]; 72 | upheldBy = [ 73 | "podman-network-myproject_default.service" 74 | ]; 75 | wantedBy = [ 76 | "podman-compose-myproject-root.target" 77 | ]; 78 | }; 79 | virtualisation.oci-containers.containers."myproject-service-a" = { 80 | image = "docker.io/library/nginx:stable-alpine-slim"; 81 | environment = { 82 | "TZ" = "America/New_York"; 83 | "test.key" = "ABC"; 84 | }; 85 | environmentFiles = [ 86 | "/tmp/test.env" 87 | ]; 88 | volumes = [ 89 | "/var/volumes/service-a:/config:rw" 90 | "storage:/storage:rw" 91 | ]; 92 | labels = { 93 | "compose2nix.systemd.service.Restart" = "no"; 94 | "compose2nix.systemd.service.RuntimeMaxSec" = "360"; 95 | "compose2nix.systemd.unit.Description" = "This is the service-a container!"; 96 | "escape-me" = "\"hello\""; 97 | }; 98 | log-driver = "journald"; 99 | extraOptions = [ 100 | "--cpus=0.5" 101 | "--network-alias=service-a" 102 | "--network=myproject_default" 103 | ]; 104 | }; 105 | systemd.services."podman-myproject-service-a" = { 106 | serviceConfig = { 107 | Restart = lib.mkOverride 90 "no"; 108 | RuntimeMaxSec = lib.mkOverride 90 360; 109 | }; 110 | unitConfig = { 111 | Description = lib.mkOverride 90 "This is the service-a container!"; 112 | }; 113 | after = [ 114 | "podman-network-myproject_default.service" 115 | "podman-volume-storage.service" 116 | ]; 117 | requires = [ 118 | "podman-network-myproject_default.service" 119 | "podman-volume-storage.service" 120 | ]; 121 | partOf = [ 122 | "podman-compose-myproject-root.target" 123 | ]; 124 | upheldBy = [ 125 | "podman-network-myproject_default.service" 126 | "podman-volume-storage.service" 127 | ]; 128 | wantedBy = [ 129 | "podman-compose-myproject-root.target" 130 | ]; 131 | unitConfig.RequiresMountsFor = [ 132 | "/var/volumes/service-a" 133 | ]; 134 | }; 135 | virtualisation.oci-containers.containers."service-b" = { 136 | image = "docker.io/library/nginx:stable-alpine-slim"; 137 | environment = { 138 | "TZ" = "America/New_York"; 139 | }; 140 | volumes = [ 141 | "/var/volumes/service-b:/config:rw" 142 | "myproject_books:/books:rw" 143 | "storage:/storage:rw" 144 | ]; 145 | labels = { 146 | "compose2nix.systemd.service.RuntimeMaxSec" = "360"; 147 | "compose2nix.systemd.unit.AllowIsolate" = "no"; 148 | }; 149 | dependsOn = [ 150 | "myproject-service-a" 151 | ]; 152 | log-driver = "journald"; 153 | extraOptions = [ 154 | "--health-cmd=echo abc && true" 155 | "--ip=192.168.8.20" 156 | "--network-alias=service-b" 157 | "--network=myproject_something" 158 | ]; 159 | }; 160 | systemd.services."podman-service-b" = { 161 | serviceConfig = { 162 | Restart = lib.mkOverride 90 "on-failure"; 163 | RuntimeMaxSec = lib.mkOverride 90 360; 164 | }; 165 | startLimitBurst = 3; 166 | unitConfig = { 167 | AllowIsolate = lib.mkOverride 90 "no"; 168 | StartLimitIntervalSec = lib.mkOverride 90 "infinity"; 169 | }; 170 | after = [ 171 | "podman-network-myproject_something.service" 172 | "podman-volume-myproject_books.service" 173 | "podman-volume-storage.service" 174 | ]; 175 | requires = [ 176 | "podman-network-myproject_something.service" 177 | "podman-volume-myproject_books.service" 178 | "podman-volume-storage.service" 179 | ]; 180 | partOf = [ 181 | "podman-compose-myproject-root.target" 182 | ]; 183 | upheldBy = [ 184 | "podman-myproject-service-a.service" 185 | "podman-network-myproject_something.service" 186 | "podman-volume-myproject_books.service" 187 | "podman-volume-storage.service" 188 | ]; 189 | wantedBy = [ 190 | "podman-compose-myproject-root.target" 191 | ]; 192 | unitConfig.RequiresMountsFor = [ 193 | "/var/volumes/service-b" 194 | ]; 195 | }; 196 | 197 | # Networks 198 | systemd.services."podman-network-myproject_another" = { 199 | path = [ pkgs.podman ]; 200 | serviceConfig = { 201 | Type = "oneshot"; 202 | RemainAfterExit = true; 203 | ExecStop = "podman network rm -f myproject_another"; 204 | }; 205 | script = '' 206 | podman network inspect myproject_another || podman network create myproject_another --driver=bridge --ipv6 207 | ''; 208 | partOf = [ "podman-compose-myproject-root.target" ]; 209 | wantedBy = [ "podman-compose-myproject-root.target" ]; 210 | }; 211 | systemd.services."podman-network-myproject_default" = { 212 | path = [ pkgs.podman ]; 213 | serviceConfig = { 214 | Type = "oneshot"; 215 | RemainAfterExit = true; 216 | ExecStop = "podman network rm -f myproject_default"; 217 | }; 218 | script = '' 219 | podman network inspect myproject_default || podman network create myproject_default 220 | ''; 221 | partOf = [ "podman-compose-myproject-root.target" ]; 222 | wantedBy = [ "podman-compose-myproject-root.target" ]; 223 | }; 224 | systemd.services."podman-network-myproject_something" = { 225 | path = [ pkgs.podman ]; 226 | serviceConfig = { 227 | Type = "oneshot"; 228 | RemainAfterExit = true; 229 | ExecStop = "podman network rm -f myproject_something"; 230 | }; 231 | script = '' 232 | podman network inspect myproject_something || podman network create myproject_something --subnet=192.168.8.0/24 --gateway=192.168.8.1 --label=escape-me='''hello''' --label=test-label=okay 233 | ''; 234 | partOf = [ "podman-compose-myproject-root.target" ]; 235 | wantedBy = [ "podman-compose-myproject-root.target" ]; 236 | }; 237 | 238 | # Volumes 239 | systemd.services."podman-volume-myproject_books" = { 240 | path = [ pkgs.podman ]; 241 | serviceConfig = { 242 | Type = "oneshot"; 243 | RemainAfterExit = true; 244 | }; 245 | unitConfig.RequiresMountsFor = [ 246 | "/mnt/media/Books" 247 | ]; 248 | script = '' 249 | podman volume inspect myproject_books || podman volume create myproject_books --opt=device=/mnt/media/Books --opt=o=bind --opt=type=none 250 | ''; 251 | partOf = [ "podman-compose-myproject-root.target" ]; 252 | wantedBy = [ "podman-compose-myproject-root.target" ]; 253 | }; 254 | systemd.services."podman-volume-storage" = { 255 | path = [ pkgs.podman ]; 256 | serviceConfig = { 257 | Type = "oneshot"; 258 | RemainAfterExit = true; 259 | }; 260 | unitConfig.RequiresMountsFor = [ 261 | "/mnt/media" 262 | ]; 263 | script = '' 264 | podman volume inspect storage || podman volume create storage --opt=device=/mnt/media --opt=o=bind --opt=type=none --label=escape-me='''hello''' 265 | ''; 266 | partOf = [ "podman-compose-myproject-root.target" ]; 267 | wantedBy = [ "podman-compose-myproject-root.target" ]; 268 | }; 269 | 270 | # Root service 271 | # When started, this will automatically create all resources and start 272 | # the containers. When stopped, this will teardown all resources. 273 | systemd.targets."podman-compose-myproject-root" = { 274 | unitConfig = { 275 | Description = "Root target generated by compose2nix."; 276 | }; 277 | wantedBy = [ "multi-user.target" ]; 278 | }; 279 | } 280 | -------------------------------------------------------------------------------- /nixos-test/test.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | 3 | let 4 | # Use pre-pulled image to avoid having to pull images inside the test VMs. 5 | # https://nixos.org/manual/nixpkgs/stable/#ssec-pkgs-dockerTools-fetchFromRegistry 6 | nginxImage = pkgs.dockerTools.pullImage { 7 | imageName = "docker.io/library/nginx"; 8 | finalImageTag = "stable-alpine-slim"; 9 | imageDigest = "sha256:f5fb3bd2fc68f768b81bccad0161f8100ac52b2de4d7b6128421edd2ce136296"; 10 | sha256 = "sha256-yRDW3G/JA4WjVOul4zCHE/Xnpk+7qPGtkueiFje6EOE="; 11 | }; 12 | common = { 13 | virtualisation.graphics = false; 14 | virtualisation.oci-containers.containers."myproject-service-a".imageFile = nginxImage; 15 | virtualisation.oci-containers.containers."service-b".imageFile = nginxImage; 16 | virtualisation.oci-containers.containers."myproject-no-restart".imageFile = nginxImage; 17 | environment.systemPackages = [ pkgs.jq ]; 18 | system.stateVersion = "23.05"; 19 | }; 20 | in 21 | { 22 | name = "basic"; 23 | nodes = { 24 | docker = 25 | { pkgs, lib, ... }: 26 | { 27 | imports = [ 28 | ./docker-compose.nix 29 | ]; 30 | 31 | custom.prefix.myproject.enable = true; 32 | 33 | # Override restart value and ensure it takes effect. 34 | systemd.services."docker-service-b" = { 35 | serviceConfig = { 36 | Restart = lib.mkForce "on-success"; 37 | }; 38 | }; 39 | } 40 | // common; 41 | podman = 42 | { pkgs, lib, ... }: 43 | { 44 | imports = [ 45 | ./podman-compose.nix 46 | ]; 47 | # Override restart value and ensure it takes effect. 48 | systemd.services."podman-service-b" = { 49 | serviceConfig = { 50 | Restart = lib.mkForce "on-success"; 51 | }; 52 | }; 53 | } 54 | // common; 55 | }; 56 | # https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests 57 | testScript = '' 58 | d = {"docker": docker, "podman": podman} 59 | 60 | start_all() 61 | 62 | # Create required directories for Docker Compose volumes and bind mounts. 63 | for runtime, m in d.items(): 64 | m.succeed("mkdir -p /mnt/media") 65 | m.succeed("mkdir -p /mnt/media/Books") 66 | m.succeed("mkdir -p /var/volumes/service-a") 67 | m.succeed("mkdir -p /var/volumes/service-b") 68 | 69 | # Create env file used by service-a. 70 | m.succeed("echo 'ABC=100' > /tmp/test.env") 71 | 72 | for runtime, m in d.items(): 73 | # Wait for root Compose service to come up. 74 | m.wait_for_unit(f"{runtime}-compose-myproject-root.target") 75 | 76 | # Wait for container services. 77 | m.wait_for_unit(f"{runtime}-myproject-service-a.service") 78 | m.wait_for_unit(f"{runtime}-service-b.service") 79 | m.wait_for_unit(f"{runtime}-myproject-no-restart.service") 80 | 81 | # Wait until the health check succeeds. 82 | m.wait_until_succeeds(f"{runtime} inspect service-b | jq .[0].State.Health.Status | grep healthy", timeout=30) 83 | 84 | # Ensure that services have correct systemd restart settings. 85 | m.succeed(f"systemctl show -p Restart {runtime}-myproject-service-a.service | grep -E '=no$'") 86 | m.succeed(f"systemctl show -p Restart {runtime}-service-b.service | grep -E '=on-success$'") 87 | m.succeed(f"systemctl show -p Restart {runtime}-myproject-no-restart.service | grep -E '=no$'") 88 | 89 | # Ensure we can reach a container in the same network. Regression test 90 | # for DNS settings, especially for Podman. 91 | m.succeed(f"{runtime} exec -it myproject-service-a wget http://no-restart") 92 | 93 | # Verify UpheldBy behavior by stopping the volume service and ensuring 94 | # that the container goes down, then comes up after the volume is started. 95 | m.systemctl(f"stop {runtime}-volume-storage.service") 96 | m.wait_until_fails(f"{runtime}-myproject-service-a.service") 97 | m.systemctl(f"start {runtime}-volume-storage.service") 98 | m.wait_for_unit(f"{runtime}-myproject-service-a.service") 99 | 100 | # Stop the root unit. 101 | m.systemctl(f"stop {runtime}-compose-myproject-root.target") 102 | ''; 103 | } 104 | -------------------------------------------------------------------------------- /nixos-test/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export TIMEZONE="America/New_York" 4 | 5 | # Generate NixOS configs for each runtime. 6 | make build 7 | bin/compose2nix \ 8 | -runtime=docker \ 9 | -inputs=nixos-test/compose.yml \ 10 | -output=nixos-test/docker-compose.nix \ 11 | -check_systemd_mounts \ 12 | -include_env_files=true \ 13 | -generate_unused_resources=true \ 14 | -use_upheld_by \ 15 | -option_prefix "custom.prefix" \ 16 | -enable_option=true 17 | bin/compose2nix \ 18 | -runtime=podman \ 19 | -inputs=nixos-test/compose.yml \ 20 | -output=nixos-test/podman-compose.nix \ 21 | -check_systemd_mounts \ 22 | -include_env_files=true \ 23 | -generate_unused_resources=true \ 24 | -use_upheld_by 25 | -------------------------------------------------------------------------------- /systemd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "slices" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/compose-spec/compose-go/v2/types" 12 | ) 13 | 14 | const ( 15 | // https://www.freedesktop.org/software/systemd/man/latest/systemd-system.conf.html#DefaultTimeoutStartSec= 16 | defaultSystemdStopTimeout = 90 * time.Second 17 | ) 18 | 19 | var ( 20 | // We purposefully do not support no/yes or 0/1 for false/true to avoid 21 | // ambiguity. Nix supports strings and bools for boolean keys anyways. 22 | systemdTrue = []string{"true", "on"} 23 | systemdFalse = []string{"false", "off"} 24 | 25 | // Examples: 26 | // compose2nix.systemd.service.RuntimeMaxSec=100 27 | // compose2nix.systemd.unit.StartLimitBurst=10 28 | systemdLabelRegexp = regexp.MustCompile(fmt.Sprintf(`%s\.systemd\.(service|unit)\.(\w+)`, composeLabelPrefix)) 29 | ) 30 | 31 | // https://www.freedesktop.org/software/systemd/man/latest/systemd.syntax.html 32 | func parseSystemdValue(v string) any { 33 | v = strings.TrimSpace(v) 34 | 35 | // Number 36 | if i, err := strconv.ParseInt(v, 10, 64); err == nil { 37 | return int(i) 38 | } 39 | 40 | // Boolean 41 | switch { 42 | case slices.Contains(systemdTrue, v): 43 | return true 44 | case slices.Contains(systemdFalse, v): 45 | return false 46 | } 47 | 48 | // String 49 | // Remove all quotes from the string. 50 | v = strings.ReplaceAll(v, `"`, "") 51 | v = strings.ReplaceAll(v, `'`, "") 52 | 53 | return v 54 | } 55 | 56 | // TODO(aksiksi): Add support for repeated keys. 57 | type ServiceConfig struct { 58 | // Map for generic options. 59 | Options map[string]any 60 | } 61 | 62 | func (s *ServiceConfig) Set(key string, value any) { 63 | if s.Options == nil { 64 | s.Options = map[string]any{} 65 | } 66 | s.Options[key] = value 67 | } 68 | 69 | // TODO(aksiksi): Add support for repeated keys. 70 | type UnitConfig struct { 71 | After []string 72 | Requires []string 73 | PartOf []string 74 | UpheldBy []string 75 | WantedBy []string 76 | RequiresMountsFor []string 77 | // Map for generic options. 78 | Options map[string]any 79 | } 80 | 81 | func (u *UnitConfig) Set(key string, value any) { 82 | if u.Options == nil { 83 | u.Options = map[string]any{} 84 | } 85 | switch key { 86 | case "After": 87 | u.After = append(u.After, value.(string)) 88 | case "Requires": 89 | u.Requires = append(u.Requires, value.(string)) 90 | case "PartOf": 91 | u.PartOf = append(u.PartOf, value.(string)) 92 | case "UpheldBy": 93 | u.UpheldBy = append(u.UpheldBy, value.(string)) 94 | case "WantedBy": 95 | u.WantedBy = append(u.WantedBy, value.(string)) 96 | case "RequiresMountsFor": 97 | u.RequiresMountsFor = append(u.RequiresMountsFor, value.(string)) 98 | default: 99 | u.Options[key] = value 100 | } 101 | } 102 | 103 | func (c *NixContainerSystemdConfig) ParseRestartPolicy(service *types.ServiceConfig, runtime ContainerRuntime) error { 104 | indefiniteRestart := false 105 | 106 | // https://docs.docker.com/compose/compose-file/compose-file-v2/#restart 107 | switch restart := service.Restart; restart { 108 | case "", "no": 109 | c.Service.Set("Restart", "no") 110 | case "always", "on-failure": 111 | // Both of these match the systemd restart options. 112 | c.Service.Set("Restart", restart) 113 | indefiniteRestart = true 114 | case "unless-stopped": 115 | // We don't have an equivalent in systemd. Podman does the same thing. 116 | c.Service.Set("Restart", "always") 117 | indefiniteRestart = true 118 | default: 119 | if strings.HasPrefix(restart, "on-failure:") && len(strings.Split(restart, ":")) == 2 { 120 | c.Service.Set("Restart", "on-failure") 121 | maxAttemptsString := strings.TrimSpace(strings.Split(restart, ":")[1]) 122 | maxAttempts, err := strconv.ParseInt(maxAttemptsString, 10, 64) 123 | if err != nil { 124 | return fmt.Errorf("failed to parse on-failure attempts: %q: %w", maxAttemptsString, err) 125 | } 126 | burst := int(maxAttempts) 127 | c.StartLimitBurst = &burst 128 | c.Unit.Set("StartLimitIntervalSec", "infinity") 129 | } else { 130 | return fmt.Errorf("unsupported restart: %q", restart) 131 | } 132 | } 133 | 134 | // The newer "deploy" config will always override the legacy "restart" config. 135 | // https://docs.docker.com/compose/compose-file/compose-file-v3/#restart_policy 136 | if deploy := service.Deploy; deploy != nil && deploy.RestartPolicy != nil { 137 | switch condition := deploy.RestartPolicy.Condition; condition { 138 | case "none": 139 | c.Service.Set("Restart", "no") 140 | case "", "any": 141 | // If unset, defaults to "any". 142 | c.Service.Set("Restart", "always") 143 | indefiniteRestart = true 144 | case "on-failure": 145 | c.Service.Set("Restart", "on-failure") 146 | default: 147 | return fmt.Errorf("unsupported condition: %q", condition) 148 | } 149 | if delay := deploy.RestartPolicy.Delay; delay != nil { 150 | c.Service.Set("RestartSec", delay.String()) 151 | } else { 152 | c.Service.Set("RestartSec", 0) 153 | } 154 | if maxAttempts := deploy.RestartPolicy.MaxAttempts; maxAttempts != nil { 155 | v := int(*maxAttempts) 156 | c.StartLimitBurst = &v 157 | } 158 | if window := deploy.RestartPolicy.Window; window != nil { 159 | // TODO(aksiksi): Investigate if StartLimitIntervalSec lines up with Compose's "window". 160 | windowSecs := int(time.Duration(*window).Seconds()) 161 | c.Unit.Set("StartLimitIntervalSec", windowSecs) 162 | } else if c.StartLimitBurst != nil { 163 | c.Unit.Set("StartLimitIntervalSec", "infinity") 164 | } 165 | } 166 | 167 | if indefiniteRestart && runtime == ContainerRuntimeDocker { 168 | // This simulates the default behavior of Docker. Basically, Docker will restart 169 | // the container with a sleep period of 100ms. This sleep period is doubled until a 170 | // maximum of 1 minute. 171 | // See: https://docs.docker.com/reference/cli/docker/container/run/#restart 172 | c.Service.Set("RestartSec", "100ms") 173 | c.Service.Set("RestartSteps", 9) // 2^(9 attempts) = 512 (* 100ms) ~= 1 minute 174 | c.Service.Set("RestartMaxDelaySec", "1m") 175 | } 176 | 177 | return nil 178 | } 179 | 180 | func (c *NixContainerSystemdConfig) ParseSystemdLabels(service *types.ServiceConfig) error { 181 | for label, value := range service.Labels { 182 | if !strings.HasPrefix(label, composeLabelPrefix) { 183 | continue 184 | } 185 | m := systemdLabelRegexp.FindStringSubmatch(label) 186 | if len(m) == 0 { 187 | continue 188 | } 189 | typ, key := m[1], m[2] 190 | switch typ { 191 | case "service": 192 | c.Service.Set(key, parseSystemdValue(value)) 193 | case "unit": 194 | c.Unit.Set(key, parseSystemdValue(value)) 195 | default: 196 | return fmt.Errorf(`invalid systemd type %q - must be "service" or "unit"`, typ) 197 | } 198 | } 199 | return nil 200 | } 201 | 202 | func (c *NixContainerSystemdConfig) Sort() { 203 | slices.Sort(c.Unit.After) 204 | slices.Sort(c.Unit.Requires) 205 | slices.Sort(c.Unit.PartOf) 206 | slices.Sort(c.Unit.UpheldBy) 207 | slices.Sort(c.Unit.WantedBy) 208 | slices.Sort(c.Unit.RequiresMountsFor) 209 | } 210 | -------------------------------------------------------------------------------- /template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/Masterminds/sprig/v3" 10 | ) 11 | 12 | //go:embed templates/*.tmpl 13 | var templateFS embed.FS 14 | var nixTemplates = template.New("nix").Funcs(sprig.FuncMap()).Funcs(funcMap) 15 | 16 | func execTemplate(t *template.Template) func(string, any) (string, error) { 17 | return func(name string, v any) (string, error) { 18 | var s strings.Builder 19 | err := t.ExecuteTemplate(&s, name, v) 20 | return s.String(), err 21 | } 22 | } 23 | 24 | // indentNonEmpty indents the given text by the provided number of spaces while 25 | // skipping empty lines. 26 | func indentNonEmpty(spaces int, text string) string { 27 | pad := strings.Repeat(" ", spaces) 28 | lines := strings.Split(text, "\n") 29 | for i, line := range lines { 30 | if strings.TrimSpace(line) == "" { 31 | continue 32 | } 33 | lines[i] = pad + line 34 | } 35 | return strings.Join(lines, "\n") 36 | } 37 | 38 | func derefInt(v *int) int { 39 | return *v 40 | } 41 | 42 | func toNixValue(v any) any { 43 | switch v := v.(type) { 44 | case string: 45 | return fmt.Sprintf("%q", escapeNixString(v)) 46 | default: 47 | return v 48 | } 49 | } 50 | 51 | func toNixList(s []string) string { 52 | b := strings.Builder{} 53 | for i, e := range s { 54 | // We purposefully do not use %q to avoid Go's built-in string escaping. 55 | b.WriteString(fmt.Sprintf(`"%s"`, escapeNixString(e))) 56 | if i < len(s)-1 { 57 | b.WriteString(" ") 58 | } 59 | } 60 | return fmt.Sprintf("[ %s ]", b.String()) 61 | } 62 | 63 | func escapeNixString(s string) string { 64 | // https://nix.dev/manual/nix/latest/language/syntax#string-literal 65 | s = strings.ReplaceAll(s, `\`, `\\`) 66 | s = strings.ReplaceAll(s, `"`, `\"`) 67 | s = strings.ReplaceAll(s, `${`, `\${`) 68 | return s 69 | } 70 | 71 | func escapeIndentedNixString(s string) string { 72 | // https://nix.dev/manual/nix/latest/language/syntax#string-literal 73 | s = strings.ReplaceAll(s, `''`, `'''`) 74 | s = strings.ReplaceAll(s, `$`, `''$`) 75 | return s 76 | } 77 | 78 | var funcMap template.FuncMap = template.FuncMap{ 79 | "derefInt": derefInt, 80 | "toNixValue": toNixValue, 81 | "toNixList": toNixList, 82 | "escapeNixString": escapeNixString, 83 | "escapeIndentedNixString": escapeIndentedNixString, 84 | } 85 | -------------------------------------------------------------------------------- /templates/build.nix.tmpl: -------------------------------------------------------------------------------- 1 | systemd.services."{{.UnitName}}" = { 2 | {{- /* TODO: Support Git repo as a build source. */}} 3 | path = [ pkgs.{{.Runtime}} pkgs.git ]; 4 | serviceConfig = { 5 | Type = "oneshot"; 6 | {{- if cfg.IncludeBuild}} 7 | RemainAfterExit = true; 8 | {{- end}} 9 | TimeoutSec = 300; 10 | }; 11 | script = '' 12 | {{- if not .IsGitRepo}} 13 | cd {{.Context}} 14 | {{- end}} 15 | {{escapeIndentedNixString .Command}} 16 | ''; 17 | {{- if and cfg.IncludeBuild rootTarget}} 18 | partOf = [ "{{rootTarget}}.target" ]; 19 | wantedBy = [ "{{rootTarget}}.target" ]; 20 | {{- end}} 21 | }; -------------------------------------------------------------------------------- /templates/config.nix.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .WriteNixSetup -}} 2 | # Runtime 3 | virtualisation.{{.Runtime}} = { 4 | enable = true; 5 | autoPrune.enable = true; 6 | {{- if eq (.Runtime | printf "%s") "podman"}} 7 | dockerCompat = true; 8 | {{- end}} 9 | }; 10 | {{- if eq (.Runtime | printf "%s") "podman"}} 11 | 12 | # Enable container name DNS for all Podman networks. 13 | networking.firewall.interfaces = let 14 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 15 | in { 16 | "${matchAll}".allowedUDPPorts = [ 53 ]; 17 | }; 18 | 19 | virtualisation.oci-containers.backend = "{{.Runtime}}"; 20 | {{- else}} 21 | virtualisation.oci-containers.backend = "{{.Runtime}}"; 22 | {{- end}} 23 | {{- end}} 24 | 25 | {{- if .Containers}} 26 | 27 | # Containers 28 | {{- range .Containers}} 29 | {{execTemplate "container.nix.tmpl" .}} 30 | {{- end}} 31 | {{- end}} 32 | 33 | {{- if .Networks}} 34 | 35 | # Networks 36 | {{- range .Networks}} 37 | {{execTemplate "network.nix.tmpl" .}} 38 | {{- end}} 39 | {{- end}} 40 | 41 | {{- if .Volumes}} 42 | 43 | # Volumes 44 | {{- range .Volumes}} 45 | {{execTemplate "volume.nix.tmpl" .}} 46 | {{- end}} 47 | {{- end}} 48 | 49 | {{- if .Builds}} 50 | 51 | # Builds 52 | {{- range .Builds}} 53 | {{execTemplate "build.nix.tmpl" .}} 54 | {{- end}} 55 | {{- end}} 56 | 57 | {{- if .CreateRootTarget}} 58 | 59 | # Root service 60 | # When started, this will automatically create all resources and start 61 | # the containers. When stopped, this will teardown all resources. 62 | systemd.targets."{{rootTarget}}" = { 63 | unitConfig = { 64 | Description = "Root target generated by compose2nix."; 65 | }; 66 | {{- if .AutoStart}} 67 | wantedBy = [ "multi-user.target" ]; 68 | {{- end}} 69 | }; 70 | {{- end -}} 71 | -------------------------------------------------------------------------------- /templates/container.nix.tmpl: -------------------------------------------------------------------------------- 1 | virtualisation.oci-containers.containers."{{.Name}}" = { 2 | image = "{{.Image}}"; 3 | 4 | {{- if .Environment}} 5 | environment = { 6 | {{- range $k, $v := .Environment}} 7 | "{{$k}}" = "{{escapeNixString $v}}"; 8 | {{- end}} 9 | }; 10 | {{- end}} 11 | 12 | {{- if .EnvFiles}} 13 | environmentFiles = [ 14 | {{- range .EnvFiles}} 15 | "{{.}}" 16 | {{- end}} 17 | ]; 18 | {{- end}} 19 | 20 | {{- if .Volumes}} 21 | volumes = [ 22 | {{- range $k, $v := .Volumes}} 23 | "{{$v}}" 24 | {{- end}} 25 | ]; 26 | {{- end}} 27 | 28 | {{- if .Ports}} 29 | ports = [ 30 | {{- range .Ports}} 31 | "{{.}}" 32 | {{- end}} 33 | ]; 34 | {{- end}} 35 | 36 | {{- if ne .Command nil}} 37 | cmd = {{toNixList .Command}}; 38 | {{- end}} 39 | 40 | {{- if .Labels}} 41 | labels = { 42 | {{- range $k, $v := .Labels}} 43 | "{{$k}}" = "{{escapeNixString $v}}"; 44 | {{- end}} 45 | }; 46 | {{- end}} 47 | 48 | {{- if .DependsOn}} 49 | dependsOn = [ 50 | {{- range .DependsOn}} 51 | "{{.}}" 52 | {{- end}} 53 | ]; 54 | {{- end}} 55 | 56 | {{- if .User}} 57 | user = "{{.User}}"; 58 | {{- end}} 59 | 60 | {{- if .LogDriver}} 61 | log-driver = "{{.LogDriver}}"; 62 | {{- end}} 63 | 64 | {{- if not .AutoStart}} 65 | autoStart = false; 66 | {{- end}} 67 | 68 | {{- if .ExtraOptions}} 69 | extraOptions = [ 70 | {{- range .ExtraOptions}} 71 | "{{escapeNixString .}}" 72 | {{- end}} 73 | ]; 74 | {{- end}} 75 | }; 76 | systemd.services."{{.Runtime}}-{{.Name}}" = { 77 | {{- if .SystemdConfig.Service}} 78 | serviceConfig = { 79 | {{- range $k, $v := .SystemdConfig.Service.Options}} 80 | {{$k}} = lib.mkOverride 90 {{toNixValue $v}}; 81 | {{- end}} 82 | }; 83 | {{- end}} 84 | {{- if .SystemdConfig.StartLimitBurst}} 85 | startLimitBurst = {{derefInt .SystemdConfig.StartLimitBurst}}; 86 | {{- end}} 87 | {{- if .SystemdConfig.Unit}} 88 | {{- if .SystemdConfig.Unit.Options}} 89 | unitConfig = { 90 | {{- range $k, $v := .SystemdConfig.Unit.Options}} 91 | {{$k}} = lib.mkOverride 90 {{toNixValue $v}}; 92 | {{- end}} 93 | }; 94 | {{- end}} 95 | {{- if .SystemdConfig.Unit.After}} 96 | after = [ 97 | {{- range .SystemdConfig.Unit.After}} 98 | "{{.}}" 99 | {{- end}} 100 | ]; 101 | {{- end}} 102 | {{- if .SystemdConfig.Unit.Requires}} 103 | requires = [ 104 | {{- range .SystemdConfig.Unit.Requires}} 105 | "{{.}}" 106 | {{- end}} 107 | ]; 108 | {{- end}} 109 | {{- if .SystemdConfig.Unit.PartOf}} 110 | partOf = [ 111 | {{- range .SystemdConfig.Unit.PartOf}} 112 | "{{.}}" 113 | {{- end}} 114 | ]; 115 | {{- end}} 116 | {{- if .SystemdConfig.Unit.UpheldBy}} 117 | upheldBy = [ 118 | {{- range .SystemdConfig.Unit.UpheldBy}} 119 | "{{.}}" 120 | {{- end}} 121 | ]; 122 | {{- end}} 123 | {{- if .SystemdConfig.Unit.WantedBy}} 124 | wantedBy = [ 125 | {{- range .SystemdConfig.Unit.WantedBy}} 126 | "{{.}}" 127 | {{- end}} 128 | ]; 129 | {{- end}} 130 | {{- if .SystemdConfig.Unit.RequiresMountsFor}} 131 | unitConfig.RequiresMountsFor = [ 132 | {{- range .SystemdConfig.Unit.RequiresMountsFor}} 133 | "{{.}}" 134 | {{- end}} 135 | ]; 136 | {{- end}} 137 | {{- end}} 138 | }; -------------------------------------------------------------------------------- /templates/main.nix.tmpl: -------------------------------------------------------------------------------- 1 | {{- if .Version -}} 2 | # Auto-generated using compose2nix v{{.Version}}. 3 | {{end -}} 4 | {{- if or (eq (.Runtime | printf "%s") "podman") .EnableOption -}} 5 | { pkgs, lib, config, ... }: 6 | {{- else -}} 7 | { pkgs, lib, ... }: 8 | {{- end}} 9 | 10 | { 11 | {{- if .EnableOption}} 12 | options.{{.Option}} = { 13 | enable = lib.mkEnableOption "Enable {{.Project.Name}}"; 14 | }; 15 | 16 | config = lib.mkIf config.{{.Option}}.enable { 17 | {{execTemplate "config.nix.tmpl" . | indentNonEmpty 4}} 18 | }; 19 | {{- else}} 20 | {{execTemplate "config.nix.tmpl" . | indentNonEmpty 2}} 21 | {{- end}} 22 | } 23 | -------------------------------------------------------------------------------- /templates/network.nix.tmpl: -------------------------------------------------------------------------------- 1 | systemd.services."{{.Runtime}}-network-{{.Name}}" = { 2 | path = [ pkgs.{{.Runtime}} ]; 3 | serviceConfig = { 4 | Type = "oneshot"; 5 | RemainAfterExit = true; 6 | ExecStop = "{{.Runtime}} network rm -f {{.Name}}"; 7 | }; 8 | script = '' 9 | {{escapeIndentedNixString .Command }} 10 | ''; 11 | {{- if rootTarget}} 12 | {{- /* PartOf for stop/restart of root, WantedBy for start of root. */}} 13 | partOf = [ "{{rootTarget}}.target" ]; 14 | wantedBy = [ "{{rootTarget}}.target" ]; 15 | {{- end}} 16 | }; -------------------------------------------------------------------------------- /templates/volume.nix.tmpl: -------------------------------------------------------------------------------- 1 | systemd.services."{{.Runtime}}-volume-{{.Name}}" = { 2 | path = [ pkgs.{{.Runtime}} ]; 3 | serviceConfig = { 4 | Type = "oneshot"; 5 | RemainAfterExit = true; 6 | {{- if .RemoveOnStop}} 7 | ExecStop = "{{.Runtime}} volume rm -f {{.Name}}"; 8 | {{- end}} 9 | }; 10 | {{- if .RequiresMountsFor}} 11 | unitConfig.RequiresMountsFor = [ 12 | {{- range .RequiresMountsFor}} 13 | "{{.}}" 14 | {{- end}} 15 | ]; 16 | {{- end}} 17 | script = '' 18 | {{escapeIndentedNixString .Command }} 19 | ''; 20 | {{- if rootTarget}} 21 | {{- /* PartOf for stop/restart of root, WantedBy for start of root. */}} 22 | partOf = [ "{{rootTarget}}.target" ]; 23 | wantedBy = [ "{{rootTarget}}.target" ]; 24 | {{- end}} 25 | }; -------------------------------------------------------------------------------- /testdata/TestAutoStart.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | auto-start: 3 | image: nginx:latest 4 | labels: 5 | - "compose2nix.settings.autoStart=true" 6 | restart: unless-stopped 7 | no-auto-start: 8 | image: nginx:latest 9 | labels: 10 | - "compose2nix.settings.autoStart=false" 11 | restart: unless-stopped 12 | default-no-auto-start: 13 | image: nginx:latest 14 | restart: unless-stopped 15 | 16 | -------------------------------------------------------------------------------- /testdata/TestAutoStart.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-auto-start" = { 13 | image = "nginx:latest"; 14 | labels = { 15 | "compose2nix.settings.autoStart" = "true"; 16 | }; 17 | log-driver = "journald"; 18 | extraOptions = [ 19 | "--network-alias=auto-start" 20 | "--network=test_default" 21 | ]; 22 | }; 23 | systemd.services."docker-test-auto-start" = { 24 | serviceConfig = { 25 | Restart = lib.mkOverride 90 "always"; 26 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 27 | RestartSec = lib.mkOverride 90 "100ms"; 28 | RestartSteps = lib.mkOverride 90 9; 29 | }; 30 | after = [ 31 | "docker-network-test_default.service" 32 | ]; 33 | requires = [ 34 | "docker-network-test_default.service" 35 | ]; 36 | partOf = [ 37 | "docker-compose-test-root.target" 38 | ]; 39 | wantedBy = [ 40 | "docker-compose-test-root.target" 41 | ]; 42 | }; 43 | virtualisation.oci-containers.containers."test-default-no-auto-start" = { 44 | image = "nginx:latest"; 45 | log-driver = "journald"; 46 | autoStart = false; 47 | extraOptions = [ 48 | "--network-alias=default-no-auto-start" 49 | "--network=test_default" 50 | ]; 51 | }; 52 | systemd.services."docker-test-default-no-auto-start" = { 53 | serviceConfig = { 54 | Restart = lib.mkOverride 90 "always"; 55 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 56 | RestartSec = lib.mkOverride 90 "100ms"; 57 | RestartSteps = lib.mkOverride 90 9; 58 | }; 59 | after = [ 60 | "docker-network-test_default.service" 61 | ]; 62 | requires = [ 63 | "docker-network-test_default.service" 64 | ]; 65 | }; 66 | virtualisation.oci-containers.containers."test-no-auto-start" = { 67 | image = "nginx:latest"; 68 | labels = { 69 | "compose2nix.settings.autoStart" = "false"; 70 | }; 71 | log-driver = "journald"; 72 | autoStart = false; 73 | extraOptions = [ 74 | "--network-alias=no-auto-start" 75 | "--network=test_default" 76 | ]; 77 | }; 78 | systemd.services."docker-test-no-auto-start" = { 79 | serviceConfig = { 80 | Restart = lib.mkOverride 90 "always"; 81 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 82 | RestartSec = lib.mkOverride 90 "100ms"; 83 | RestartSteps = lib.mkOverride 90 9; 84 | }; 85 | after = [ 86 | "docker-network-test_default.service" 87 | ]; 88 | requires = [ 89 | "docker-network-test_default.service" 90 | ]; 91 | }; 92 | 93 | # Networks 94 | systemd.services."docker-network-test_default" = { 95 | path = [ pkgs.docker ]; 96 | serviceConfig = { 97 | Type = "oneshot"; 98 | RemainAfterExit = true; 99 | ExecStop = "docker network rm -f test_default"; 100 | }; 101 | script = '' 102 | docker network inspect test_default || docker network create test_default 103 | ''; 104 | partOf = [ "docker-compose-test-root.target" ]; 105 | wantedBy = [ "docker-compose-test-root.target" ]; 106 | }; 107 | 108 | # Root service 109 | # When started, this will automatically create all resources and start 110 | # the containers. When stopped, this will teardown all resources. 111 | systemd.targets."docker-compose-test-root" = { 112 | unitConfig = { 113 | Description = "Root target generated by compose2nix."; 114 | }; 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /testdata/TestAutoStart.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-auto-start" = { 22 | image = "nginx:latest"; 23 | labels = { 24 | "compose2nix.settings.autoStart" = "true"; 25 | }; 26 | log-driver = "journald"; 27 | extraOptions = [ 28 | "--network-alias=auto-start" 29 | "--network=test_default" 30 | ]; 31 | }; 32 | systemd.services."podman-test-auto-start" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "always"; 35 | }; 36 | after = [ 37 | "podman-network-test_default.service" 38 | ]; 39 | requires = [ 40 | "podman-network-test_default.service" 41 | ]; 42 | partOf = [ 43 | "podman-compose-test-root.target" 44 | ]; 45 | wantedBy = [ 46 | "podman-compose-test-root.target" 47 | ]; 48 | }; 49 | virtualisation.oci-containers.containers."test-default-no-auto-start" = { 50 | image = "nginx:latest"; 51 | log-driver = "journald"; 52 | autoStart = false; 53 | extraOptions = [ 54 | "--network-alias=default-no-auto-start" 55 | "--network=test_default" 56 | ]; 57 | }; 58 | systemd.services."podman-test-default-no-auto-start" = { 59 | serviceConfig = { 60 | Restart = lib.mkOverride 90 "always"; 61 | }; 62 | after = [ 63 | "podman-network-test_default.service" 64 | ]; 65 | requires = [ 66 | "podman-network-test_default.service" 67 | ]; 68 | }; 69 | virtualisation.oci-containers.containers."test-no-auto-start" = { 70 | image = "nginx:latest"; 71 | labels = { 72 | "compose2nix.settings.autoStart" = "false"; 73 | }; 74 | log-driver = "journald"; 75 | autoStart = false; 76 | extraOptions = [ 77 | "--network-alias=no-auto-start" 78 | "--network=test_default" 79 | ]; 80 | }; 81 | systemd.services."podman-test-no-auto-start" = { 82 | serviceConfig = { 83 | Restart = lib.mkOverride 90 "always"; 84 | }; 85 | after = [ 86 | "podman-network-test_default.service" 87 | ]; 88 | requires = [ 89 | "podman-network-test_default.service" 90 | ]; 91 | }; 92 | 93 | # Networks 94 | systemd.services."podman-network-test_default" = { 95 | path = [ pkgs.podman ]; 96 | serviceConfig = { 97 | Type = "oneshot"; 98 | RemainAfterExit = true; 99 | ExecStop = "podman network rm -f test_default"; 100 | }; 101 | script = '' 102 | podman network inspect test_default || podman network create test_default 103 | ''; 104 | partOf = [ "podman-compose-test-root.target" ]; 105 | wantedBy = [ "podman-compose-test-root.target" ]; 106 | }; 107 | 108 | # Root service 109 | # When started, this will automatically create all resources and start 110 | # the containers. When stopped, this will teardown all resources. 111 | systemd.targets."podman-compose-test-root" = { 112 | unitConfig = { 113 | Description = "Root target generated by compose2nix."; 114 | }; 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /testdata/TestBuildSpec.compose.yml: -------------------------------------------------------------------------------- 1 | # Adapted from: https://github.com/ente-io/ente/blob/main/server/compose.yaml 2 | services: 3 | museum: 4 | build: 5 | context: . 6 | args: 7 | GIT_COMMIT: development-cluster 8 | tags: 9 | - latest 10 | - non-latest 11 | ports: 12 | - 8080:8080 # API 13 | - 2112:2112 # Prometheus metrics 14 | environment: 15 | # Pass-in the config to connect to the DB and MinIO 16 | ENTE_CREDENTIALS_FILE: /credentials.yaml 17 | volumes: 18 | - custom-logs:/var/logs 19 | - ./museum.yaml:/museum.yaml:ro 20 | - ./scripts/compose/credentials.yaml:/credentials.yaml:ro 21 | - ./data:/data:ro 22 | networks: 23 | - internal 24 | prefetcharr: 25 | image: prefetcharr 26 | build: https://github.com/p-hueber/prefetcharr.git 27 | environment: 28 | - JELLYFIN_URL=http://example.com/jellyfin 29 | volumes: 30 | - /path/to/log/dir:/log 31 | 32 | volumes: 33 | custom-logs: 34 | 35 | networks: 36 | internal: 37 | -------------------------------------------------------------------------------- /testdata/TestBuildSpec.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-museum" = { 13 | image = "compose2nix/test-museum"; 14 | environment = { 15 | "ENTE_CREDENTIALS_FILE" = "/credentials.yaml"; 16 | }; 17 | volumes = [ 18 | "/some/path/data:/data:ro" 19 | "/some/path/museum.yaml:/museum.yaml:ro" 20 | "/some/path/scripts/compose/credentials.yaml:/credentials.yaml:ro" 21 | "test_custom-logs:/var/logs:rw" 22 | ]; 23 | ports = [ 24 | "8080:8080/tcp" 25 | "2112:2112/tcp" 26 | ]; 27 | log-driver = "journald"; 28 | autoStart = false; 29 | extraOptions = [ 30 | "--network-alias=museum" 31 | "--network=test_internal" 32 | ]; 33 | }; 34 | systemd.services."docker-test-museum" = { 35 | serviceConfig = { 36 | Restart = lib.mkOverride 90 "no"; 37 | }; 38 | after = [ 39 | "docker-network-test_internal.service" 40 | "docker-volume-test_custom-logs.service" 41 | ]; 42 | requires = [ 43 | "docker-network-test_internal.service" 44 | "docker-volume-test_custom-logs.service" 45 | ]; 46 | }; 47 | virtualisation.oci-containers.containers."test-prefetcharr" = { 48 | image = "prefetcharr"; 49 | environment = { 50 | "JELLYFIN_URL" = "http://example.com/jellyfin"; 51 | }; 52 | volumes = [ 53 | "/path/to/log/dir:/log:rw" 54 | ]; 55 | log-driver = "journald"; 56 | autoStart = false; 57 | extraOptions = [ 58 | "--network-alias=prefetcharr" 59 | "--network=test_default" 60 | ]; 61 | }; 62 | systemd.services."docker-test-prefetcharr" = { 63 | serviceConfig = { 64 | Restart = lib.mkOverride 90 "no"; 65 | }; 66 | after = [ 67 | "docker-network-test_default.service" 68 | ]; 69 | requires = [ 70 | "docker-network-test_default.service" 71 | ]; 72 | }; 73 | 74 | # Networks 75 | systemd.services."docker-network-test_default" = { 76 | path = [ pkgs.docker ]; 77 | serviceConfig = { 78 | Type = "oneshot"; 79 | RemainAfterExit = true; 80 | ExecStop = "docker network rm -f test_default"; 81 | }; 82 | script = '' 83 | docker network inspect test_default || docker network create test_default 84 | ''; 85 | partOf = [ "docker-compose-test-root.target" ]; 86 | wantedBy = [ "docker-compose-test-root.target" ]; 87 | }; 88 | systemd.services."docker-network-test_internal" = { 89 | path = [ pkgs.docker ]; 90 | serviceConfig = { 91 | Type = "oneshot"; 92 | RemainAfterExit = true; 93 | ExecStop = "docker network rm -f test_internal"; 94 | }; 95 | script = '' 96 | docker network inspect test_internal || docker network create test_internal 97 | ''; 98 | partOf = [ "docker-compose-test-root.target" ]; 99 | wantedBy = [ "docker-compose-test-root.target" ]; 100 | }; 101 | 102 | # Volumes 103 | systemd.services."docker-volume-test_custom-logs" = { 104 | path = [ pkgs.docker ]; 105 | serviceConfig = { 106 | Type = "oneshot"; 107 | RemainAfterExit = true; 108 | }; 109 | script = '' 110 | docker volume inspect test_custom-logs || docker volume create test_custom-logs 111 | ''; 112 | partOf = [ "docker-compose-test-root.target" ]; 113 | wantedBy = [ "docker-compose-test-root.target" ]; 114 | }; 115 | 116 | # Builds 117 | systemd.services."docker-build-test-museum" = { 118 | path = [ pkgs.docker pkgs.git ]; 119 | serviceConfig = { 120 | Type = "oneshot"; 121 | TimeoutSec = 300; 122 | }; 123 | script = '' 124 | cd /some/path 125 | docker build -t compose2nix/test-museum -t compose2nix/test-museum:latest -t compose2nix/test-museum:non-latest --build-arg GIT_COMMIT=development-cluster . 126 | ''; 127 | }; 128 | systemd.services."docker-build-test-prefetcharr" = { 129 | path = [ pkgs.docker pkgs.git ]; 130 | serviceConfig = { 131 | Type = "oneshot"; 132 | TimeoutSec = 300; 133 | }; 134 | script = '' 135 | docker build -t prefetcharr https://github.com/p-hueber/prefetcharr.git 136 | ''; 137 | }; 138 | 139 | # Root service 140 | # When started, this will automatically create all resources and start 141 | # the containers. When stopped, this will teardown all resources. 142 | systemd.targets."docker-compose-test-root" = { 143 | unitConfig = { 144 | Description = "Root target generated by compose2nix."; 145 | }; 146 | }; 147 | } 148 | -------------------------------------------------------------------------------- /testdata/TestBuildSpec.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-museum" = { 22 | image = "localhost/compose2nix/test-museum"; 23 | environment = { 24 | "ENTE_CREDENTIALS_FILE" = "/credentials.yaml"; 25 | }; 26 | volumes = [ 27 | "/some/path/data:/data:ro" 28 | "/some/path/museum.yaml:/museum.yaml:ro" 29 | "/some/path/scripts/compose/credentials.yaml:/credentials.yaml:ro" 30 | "test_custom-logs:/var/logs:rw" 31 | ]; 32 | ports = [ 33 | "8080:8080/tcp" 34 | "2112:2112/tcp" 35 | ]; 36 | log-driver = "journald"; 37 | autoStart = false; 38 | extraOptions = [ 39 | "--network-alias=museum" 40 | "--network=test_internal" 41 | ]; 42 | }; 43 | systemd.services."podman-test-museum" = { 44 | serviceConfig = { 45 | Restart = lib.mkOverride 90 "no"; 46 | }; 47 | after = [ 48 | "podman-network-test_internal.service" 49 | "podman-volume-test_custom-logs.service" 50 | ]; 51 | requires = [ 52 | "podman-network-test_internal.service" 53 | "podman-volume-test_custom-logs.service" 54 | ]; 55 | }; 56 | virtualisation.oci-containers.containers."test-prefetcharr" = { 57 | image = "localhost/prefetcharr"; 58 | environment = { 59 | "JELLYFIN_URL" = "http://example.com/jellyfin"; 60 | }; 61 | volumes = [ 62 | "/path/to/log/dir:/log:rw" 63 | ]; 64 | log-driver = "journald"; 65 | autoStart = false; 66 | extraOptions = [ 67 | "--network-alias=prefetcharr" 68 | "--network=test_default" 69 | ]; 70 | }; 71 | systemd.services."podman-test-prefetcharr" = { 72 | serviceConfig = { 73 | Restart = lib.mkOverride 90 "no"; 74 | }; 75 | after = [ 76 | "podman-network-test_default.service" 77 | ]; 78 | requires = [ 79 | "podman-network-test_default.service" 80 | ]; 81 | }; 82 | 83 | # Networks 84 | systemd.services."podman-network-test_default" = { 85 | path = [ pkgs.podman ]; 86 | serviceConfig = { 87 | Type = "oneshot"; 88 | RemainAfterExit = true; 89 | ExecStop = "podman network rm -f test_default"; 90 | }; 91 | script = '' 92 | podman network inspect test_default || podman network create test_default 93 | ''; 94 | partOf = [ "podman-compose-test-root.target" ]; 95 | wantedBy = [ "podman-compose-test-root.target" ]; 96 | }; 97 | systemd.services."podman-network-test_internal" = { 98 | path = [ pkgs.podman ]; 99 | serviceConfig = { 100 | Type = "oneshot"; 101 | RemainAfterExit = true; 102 | ExecStop = "podman network rm -f test_internal"; 103 | }; 104 | script = '' 105 | podman network inspect test_internal || podman network create test_internal 106 | ''; 107 | partOf = [ "podman-compose-test-root.target" ]; 108 | wantedBy = [ "podman-compose-test-root.target" ]; 109 | }; 110 | 111 | # Volumes 112 | systemd.services."podman-volume-test_custom-logs" = { 113 | path = [ pkgs.podman ]; 114 | serviceConfig = { 115 | Type = "oneshot"; 116 | RemainAfterExit = true; 117 | }; 118 | script = '' 119 | podman volume inspect test_custom-logs || podman volume create test_custom-logs 120 | ''; 121 | partOf = [ "podman-compose-test-root.target" ]; 122 | wantedBy = [ "podman-compose-test-root.target" ]; 123 | }; 124 | 125 | # Builds 126 | systemd.services."podman-build-test-museum" = { 127 | path = [ pkgs.podman pkgs.git ]; 128 | serviceConfig = { 129 | Type = "oneshot"; 130 | TimeoutSec = 300; 131 | }; 132 | script = '' 133 | cd /some/path 134 | podman build -t compose2nix/test-museum -t compose2nix/test-museum:latest -t compose2nix/test-museum:non-latest --build-arg GIT_COMMIT=development-cluster . 135 | ''; 136 | }; 137 | systemd.services."podman-build-test-prefetcharr" = { 138 | path = [ pkgs.podman pkgs.git ]; 139 | serviceConfig = { 140 | Type = "oneshot"; 141 | TimeoutSec = 300; 142 | }; 143 | script = '' 144 | podman build -t prefetcharr https://github.com/p-hueber/prefetcharr.git 145 | ''; 146 | }; 147 | 148 | # Root service 149 | # When started, this will automatically create all resources and start 150 | # the containers. When stopped, this will teardown all resources. 151 | systemd.targets."podman-compose-test-root" = { 152 | unitConfig = { 153 | Description = "Root target generated by compose2nix."; 154 | }; 155 | }; 156 | } 157 | -------------------------------------------------------------------------------- /testdata/TestBuildSpec_BuildEnabled.compose.yml: -------------------------------------------------------------------------------- 1 | # Adapted from: https://github.com/ente-io/ente/blob/main/server/compose.yaml 2 | services: 3 | museum: 4 | build: 5 | context: . 6 | args: 7 | GIT_COMMIT: development-cluster 8 | dockerfile: path/Dockerfile 9 | ports: 10 | - 8080:8080 # API 11 | - 2112:2112 # Prometheus metrics 12 | environment: 13 | # Pass-in the config to connect to the DB and MinIO 14 | ENTE_CREDENTIALS_FILE: /credentials.yaml 15 | volumes: 16 | - custom-logs:/var/logs 17 | - ./museum.yaml:/museum.yaml:ro 18 | - ./scripts/compose/credentials.yaml:/credentials.yaml:ro 19 | - ./data:/data:ro 20 | networks: 21 | - internal 22 | 23 | volumes: 24 | custom-logs: 25 | 26 | networks: 27 | internal: 28 | -------------------------------------------------------------------------------- /testdata/TestBuildSpec_BuildEnabled.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-museum" = { 13 | image = "compose2nix/test-museum"; 14 | environment = { 15 | "ENTE_CREDENTIALS_FILE" = "/credentials.yaml"; 16 | }; 17 | volumes = [ 18 | "/some/path/data:/data:ro" 19 | "/some/path/museum.yaml:/museum.yaml:ro" 20 | "/some/path/scripts/compose/credentials.yaml:/credentials.yaml:ro" 21 | "test_custom-logs:/var/logs:rw" 22 | ]; 23 | ports = [ 24 | "8080:8080/tcp" 25 | "2112:2112/tcp" 26 | ]; 27 | log-driver = "journald"; 28 | autoStart = false; 29 | extraOptions = [ 30 | "--network-alias=museum" 31 | "--network=test_internal" 32 | ]; 33 | }; 34 | systemd.services."docker-test-museum" = { 35 | serviceConfig = { 36 | Restart = lib.mkOverride 90 "no"; 37 | }; 38 | after = [ 39 | "docker-build-test-museum.service" 40 | "docker-network-test_internal.service" 41 | "docker-volume-test_custom-logs.service" 42 | ]; 43 | requires = [ 44 | "docker-build-test-museum.service" 45 | "docker-network-test_internal.service" 46 | "docker-volume-test_custom-logs.service" 47 | ]; 48 | upheldBy = [ 49 | "docker-build-test-museum.service" 50 | "docker-network-test_internal.service" 51 | "docker-volume-test_custom-logs.service" 52 | ]; 53 | }; 54 | 55 | # Networks 56 | systemd.services."docker-network-test_internal" = { 57 | path = [ pkgs.docker ]; 58 | serviceConfig = { 59 | Type = "oneshot"; 60 | RemainAfterExit = true; 61 | ExecStop = "docker network rm -f test_internal"; 62 | }; 63 | script = '' 64 | docker network inspect test_internal || docker network create test_internal 65 | ''; 66 | partOf = [ "docker-compose-test-root.target" ]; 67 | wantedBy = [ "docker-compose-test-root.target" ]; 68 | }; 69 | 70 | # Volumes 71 | systemd.services."docker-volume-test_custom-logs" = { 72 | path = [ pkgs.docker ]; 73 | serviceConfig = { 74 | Type = "oneshot"; 75 | RemainAfterExit = true; 76 | }; 77 | script = '' 78 | docker volume inspect test_custom-logs || docker volume create test_custom-logs 79 | ''; 80 | partOf = [ "docker-compose-test-root.target" ]; 81 | wantedBy = [ "docker-compose-test-root.target" ]; 82 | }; 83 | 84 | # Builds 85 | systemd.services."docker-build-test-museum" = { 86 | path = [ pkgs.docker pkgs.git ]; 87 | serviceConfig = { 88 | Type = "oneshot"; 89 | RemainAfterExit = true; 90 | TimeoutSec = 300; 91 | }; 92 | script = '' 93 | cd /some/path 94 | docker build -t compose2nix/test-museum --build-arg GIT_COMMIT=development-cluster -f path/Dockerfile . 95 | ''; 96 | partOf = [ "docker-compose-test-root.target" ]; 97 | wantedBy = [ "docker-compose-test-root.target" ]; 98 | }; 99 | 100 | # Root service 101 | # When started, this will automatically create all resources and start 102 | # the containers. When stopped, this will teardown all resources. 103 | systemd.targets."docker-compose-test-root" = { 104 | unitConfig = { 105 | Description = "Root target generated by compose2nix."; 106 | }; 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /testdata/TestBuildSpec_BuildEnabled.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-museum" = { 22 | image = "localhost/compose2nix/test-museum"; 23 | environment = { 24 | "ENTE_CREDENTIALS_FILE" = "/credentials.yaml"; 25 | }; 26 | volumes = [ 27 | "/some/path/data:/data:ro" 28 | "/some/path/museum.yaml:/museum.yaml:ro" 29 | "/some/path/scripts/compose/credentials.yaml:/credentials.yaml:ro" 30 | "test_custom-logs:/var/logs:rw" 31 | ]; 32 | ports = [ 33 | "8080:8080/tcp" 34 | "2112:2112/tcp" 35 | ]; 36 | log-driver = "journald"; 37 | autoStart = false; 38 | extraOptions = [ 39 | "--network-alias=museum" 40 | "--network=test_internal" 41 | ]; 42 | }; 43 | systemd.services."podman-test-museum" = { 44 | serviceConfig = { 45 | Restart = lib.mkOverride 90 "no"; 46 | }; 47 | after = [ 48 | "podman-build-test-museum.service" 49 | "podman-network-test_internal.service" 50 | "podman-volume-test_custom-logs.service" 51 | ]; 52 | requires = [ 53 | "podman-build-test-museum.service" 54 | "podman-network-test_internal.service" 55 | "podman-volume-test_custom-logs.service" 56 | ]; 57 | upheldBy = [ 58 | "podman-build-test-museum.service" 59 | "podman-network-test_internal.service" 60 | "podman-volume-test_custom-logs.service" 61 | ]; 62 | }; 63 | 64 | # Networks 65 | systemd.services."podman-network-test_internal" = { 66 | path = [ pkgs.podman ]; 67 | serviceConfig = { 68 | Type = "oneshot"; 69 | RemainAfterExit = true; 70 | ExecStop = "podman network rm -f test_internal"; 71 | }; 72 | script = '' 73 | podman network inspect test_internal || podman network create test_internal 74 | ''; 75 | partOf = [ "podman-compose-test-root.target" ]; 76 | wantedBy = [ "podman-compose-test-root.target" ]; 77 | }; 78 | 79 | # Volumes 80 | systemd.services."podman-volume-test_custom-logs" = { 81 | path = [ pkgs.podman ]; 82 | serviceConfig = { 83 | Type = "oneshot"; 84 | RemainAfterExit = true; 85 | }; 86 | script = '' 87 | podman volume inspect test_custom-logs || podman volume create test_custom-logs 88 | ''; 89 | partOf = [ "podman-compose-test-root.target" ]; 90 | wantedBy = [ "podman-compose-test-root.target" ]; 91 | }; 92 | 93 | # Builds 94 | systemd.services."podman-build-test-museum" = { 95 | path = [ pkgs.podman pkgs.git ]; 96 | serviceConfig = { 97 | Type = "oneshot"; 98 | RemainAfterExit = true; 99 | TimeoutSec = 300; 100 | }; 101 | script = '' 102 | cd /some/path 103 | podman build -t compose2nix/test-museum --build-arg GIT_COMMIT=development-cluster -f path/Dockerfile . 104 | ''; 105 | partOf = [ "podman-compose-test-root.target" ]; 106 | wantedBy = [ "podman-compose-test-root.target" ]; 107 | }; 108 | 109 | # Root service 110 | # When started, this will automatically create all resources and start 111 | # the containers. When stopped, this will teardown all resources. 112 | systemd.targets."podman-compose-test-root" = { 113 | unitConfig = { 114 | Description = "Root target generated by compose2nix."; 115 | }; 116 | }; 117 | } 118 | -------------------------------------------------------------------------------- /testdata/TestCommandAndEntrypoint.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | both: 3 | image: nginx:latest 4 | command: ["ls", "-la", "\"escape me please\""] 5 | entrypoint: 6 | ["nginx", "-g", "daemon off;", "-c", "/etc/config/nginx/conf/nginx.conf"] 7 | string: 8 | image: nginx:latest 9 | entrypoint: "ENV_VAR=$${ABC} bash /abc.sh" 10 | empty-command-and-entrypoint: 11 | image: nginx:latest 12 | command: [] 13 | entrypoint: [] 14 | "null-command-and-entrypoint": 15 | image: nginx:latest 16 | command: null 17 | entrypoint: null 18 | -------------------------------------------------------------------------------- /testdata/TestCommandAndEntrypoint.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-both" = { 13 | image = "nginx:latest"; 14 | cmd = [ "ls" "-la" "\"escape me please\"" ]; 15 | log-driver = "journald"; 16 | autoStart = false; 17 | extraOptions = [ 18 | "--entrypoint=[\"nginx\", \"-g\", \"daemon off;\", \"-c\", \"/etc/config/nginx/conf/nginx.conf\"]" 19 | "--network-alias=both" 20 | "--network=test_default" 21 | ]; 22 | }; 23 | systemd.services."docker-test-both" = { 24 | serviceConfig = { 25 | Restart = lib.mkOverride 90 "no"; 26 | }; 27 | after = [ 28 | "docker-network-test_default.service" 29 | ]; 30 | requires = [ 31 | "docker-network-test_default.service" 32 | ]; 33 | }; 34 | virtualisation.oci-containers.containers."test-empty-command-and-entrypoint" = { 35 | image = "nginx:latest"; 36 | cmd = [ ]; 37 | log-driver = "journald"; 38 | autoStart = false; 39 | extraOptions = [ 40 | "--entrypoint=[]" 41 | "--network-alias=empty-command-and-entrypoint" 42 | "--network=test_default" 43 | ]; 44 | }; 45 | systemd.services."docker-test-empty-command-and-entrypoint" = { 46 | serviceConfig = { 47 | Restart = lib.mkOverride 90 "no"; 48 | }; 49 | after = [ 50 | "docker-network-test_default.service" 51 | ]; 52 | requires = [ 53 | "docker-network-test_default.service" 54 | ]; 55 | }; 56 | virtualisation.oci-containers.containers."test-null-command-and-entrypoint" = { 57 | image = "nginx:latest"; 58 | log-driver = "journald"; 59 | autoStart = false; 60 | extraOptions = [ 61 | "--network-alias=null-command-and-entrypoint" 62 | "--network=test_default" 63 | ]; 64 | }; 65 | systemd.services."docker-test-null-command-and-entrypoint" = { 66 | serviceConfig = { 67 | Restart = lib.mkOverride 90 "no"; 68 | }; 69 | after = [ 70 | "docker-network-test_default.service" 71 | ]; 72 | requires = [ 73 | "docker-network-test_default.service" 74 | ]; 75 | }; 76 | virtualisation.oci-containers.containers."test-string" = { 77 | image = "nginx:latest"; 78 | log-driver = "journald"; 79 | autoStart = false; 80 | extraOptions = [ 81 | "--entrypoint=[\"ENV_VAR=\${ABC}\", \"bash\", \"/abc.sh\"]" 82 | "--network-alias=string" 83 | "--network=test_default" 84 | ]; 85 | }; 86 | systemd.services."docker-test-string" = { 87 | serviceConfig = { 88 | Restart = lib.mkOverride 90 "no"; 89 | }; 90 | after = [ 91 | "docker-network-test_default.service" 92 | ]; 93 | requires = [ 94 | "docker-network-test_default.service" 95 | ]; 96 | }; 97 | 98 | # Networks 99 | systemd.services."docker-network-test_default" = { 100 | path = [ pkgs.docker ]; 101 | serviceConfig = { 102 | Type = "oneshot"; 103 | RemainAfterExit = true; 104 | ExecStop = "docker network rm -f test_default"; 105 | }; 106 | script = '' 107 | docker network inspect test_default || docker network create test_default 108 | ''; 109 | partOf = [ "docker-compose-test-root.target" ]; 110 | wantedBy = [ "docker-compose-test-root.target" ]; 111 | }; 112 | 113 | # Root service 114 | # When started, this will automatically create all resources and start 115 | # the containers. When stopped, this will teardown all resources. 116 | systemd.targets."docker-compose-test-root" = { 117 | unitConfig = { 118 | Description = "Root target generated by compose2nix."; 119 | }; 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /testdata/TestCommandAndEntrypoint.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-both" = { 22 | image = "nginx:latest"; 23 | cmd = [ "ls" "-la" "\"escape me please\"" ]; 24 | log-driver = "journald"; 25 | autoStart = false; 26 | extraOptions = [ 27 | "--entrypoint=[\"nginx\", \"-g\", \"daemon off;\", \"-c\", \"/etc/config/nginx/conf/nginx.conf\"]" 28 | "--network-alias=both" 29 | "--network=test_default" 30 | ]; 31 | }; 32 | systemd.services."podman-test-both" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "no"; 35 | }; 36 | after = [ 37 | "podman-network-test_default.service" 38 | ]; 39 | requires = [ 40 | "podman-network-test_default.service" 41 | ]; 42 | }; 43 | virtualisation.oci-containers.containers."test-empty-command-and-entrypoint" = { 44 | image = "nginx:latest"; 45 | cmd = [ ]; 46 | log-driver = "journald"; 47 | autoStart = false; 48 | extraOptions = [ 49 | "--entrypoint=[]" 50 | "--network-alias=empty-command-and-entrypoint" 51 | "--network=test_default" 52 | ]; 53 | }; 54 | systemd.services."podman-test-empty-command-and-entrypoint" = { 55 | serviceConfig = { 56 | Restart = lib.mkOverride 90 "no"; 57 | }; 58 | after = [ 59 | "podman-network-test_default.service" 60 | ]; 61 | requires = [ 62 | "podman-network-test_default.service" 63 | ]; 64 | }; 65 | virtualisation.oci-containers.containers."test-null-command-and-entrypoint" = { 66 | image = "nginx:latest"; 67 | log-driver = "journald"; 68 | autoStart = false; 69 | extraOptions = [ 70 | "--network-alias=null-command-and-entrypoint" 71 | "--network=test_default" 72 | ]; 73 | }; 74 | systemd.services."podman-test-null-command-and-entrypoint" = { 75 | serviceConfig = { 76 | Restart = lib.mkOverride 90 "no"; 77 | }; 78 | after = [ 79 | "podman-network-test_default.service" 80 | ]; 81 | requires = [ 82 | "podman-network-test_default.service" 83 | ]; 84 | }; 85 | virtualisation.oci-containers.containers."test-string" = { 86 | image = "nginx:latest"; 87 | log-driver = "journald"; 88 | autoStart = false; 89 | extraOptions = [ 90 | "--entrypoint=[\"ENV_VAR=\${ABC}\", \"bash\", \"/abc.sh\"]" 91 | "--network-alias=string" 92 | "--network=test_default" 93 | ]; 94 | }; 95 | systemd.services."podman-test-string" = { 96 | serviceConfig = { 97 | Restart = lib.mkOverride 90 "no"; 98 | }; 99 | after = [ 100 | "podman-network-test_default.service" 101 | ]; 102 | requires = [ 103 | "podman-network-test_default.service" 104 | ]; 105 | }; 106 | 107 | # Networks 108 | systemd.services."podman-network-test_default" = { 109 | path = [ pkgs.podman ]; 110 | serviceConfig = { 111 | Type = "oneshot"; 112 | RemainAfterExit = true; 113 | ExecStop = "podman network rm -f test_default"; 114 | }; 115 | script = '' 116 | podman network inspect test_default || podman network create test_default 117 | ''; 118 | partOf = [ "podman-compose-test-root.target" ]; 119 | wantedBy = [ "podman-compose-test-root.target" ]; 120 | }; 121 | 122 | # Root service 123 | # When started, this will automatically create all resources and start 124 | # the containers. When stopped, this will teardown all resources. 125 | systemd.targets."podman-compose-test-root" = { 126 | unitConfig = { 127 | Description = "Root target generated by compose2nix."; 128 | }; 129 | }; 130 | } 131 | -------------------------------------------------------------------------------- /testdata/TestComposeEnvFiles.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | first: 3 | image: nginx:latest 4 | env_file: 5 | - ./testdata/first.env 6 | - path: ./override.env 7 | required: false 8 | second: 9 | image: nginx:latest 10 | env_file: 11 | - ./testdata/second.env 12 | third: 13 | image: nginx:latest 14 | env_file: 15 | - path: ./unknown.env 16 | required: false 17 | -------------------------------------------------------------------------------- /testdata/TestComposeEnvFiles.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-first" = { 13 | image = "nginx:latest"; 14 | environment = { 15 | "DEPTH" = "10"; 16 | "NAME" = "first"; 17 | }; 18 | environmentFiles = [ 19 | "override.env" 20 | "testdata/first.env" 21 | ]; 22 | log-driver = "journald"; 23 | autoStart = false; 24 | extraOptions = [ 25 | "--network-alias=first" 26 | "--network=test_default" 27 | ]; 28 | }; 29 | systemd.services."docker-test-first" = { 30 | serviceConfig = { 31 | Restart = lib.mkOverride 90 "no"; 32 | }; 33 | after = [ 34 | "docker-network-test_default.service" 35 | ]; 36 | requires = [ 37 | "docker-network-test_default.service" 38 | ]; 39 | }; 40 | virtualisation.oci-containers.containers."test-second" = { 41 | image = "nginx:latest"; 42 | environment = { 43 | "DEPTH" = "20"; 44 | "NAME" = "second"; 45 | }; 46 | environmentFiles = [ 47 | "testdata/first.env" 48 | "testdata/second.env" 49 | ]; 50 | log-driver = "journald"; 51 | autoStart = false; 52 | extraOptions = [ 53 | "--network-alias=second" 54 | "--network=test_default" 55 | ]; 56 | }; 57 | systemd.services."docker-test-second" = { 58 | serviceConfig = { 59 | Restart = lib.mkOverride 90 "no"; 60 | }; 61 | after = [ 62 | "docker-network-test_default.service" 63 | ]; 64 | requires = [ 65 | "docker-network-test_default.service" 66 | ]; 67 | }; 68 | virtualisation.oci-containers.containers."test-third" = { 69 | image = "nginx:latest"; 70 | environmentFiles = [ 71 | "testdata/first.env" 72 | "unknown.env" 73 | ]; 74 | log-driver = "journald"; 75 | autoStart = false; 76 | extraOptions = [ 77 | "--network-alias=third" 78 | "--network=test_default" 79 | ]; 80 | }; 81 | systemd.services."docker-test-third" = { 82 | serviceConfig = { 83 | Restart = lib.mkOverride 90 "no"; 84 | }; 85 | after = [ 86 | "docker-network-test_default.service" 87 | ]; 88 | requires = [ 89 | "docker-network-test_default.service" 90 | ]; 91 | }; 92 | 93 | # Networks 94 | systemd.services."docker-network-test_default" = { 95 | path = [ pkgs.docker ]; 96 | serviceConfig = { 97 | Type = "oneshot"; 98 | RemainAfterExit = true; 99 | ExecStop = "docker network rm -f test_default"; 100 | }; 101 | script = '' 102 | docker network inspect test_default || docker network create test_default 103 | ''; 104 | partOf = [ "docker-compose-test-root.target" ]; 105 | wantedBy = [ "docker-compose-test-root.target" ]; 106 | }; 107 | 108 | # Root service 109 | # When started, this will automatically create all resources and start 110 | # the containers. When stopped, this will teardown all resources. 111 | systemd.targets."docker-compose-test-root" = { 112 | unitConfig = { 113 | Description = "Root target generated by compose2nix."; 114 | }; 115 | }; 116 | } 117 | -------------------------------------------------------------------------------- /testdata/TestComposeEnvFiles.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-first" = { 22 | image = "nginx:latest"; 23 | environment = { 24 | "DEPTH" = "10"; 25 | "NAME" = "first"; 26 | }; 27 | environmentFiles = [ 28 | "override.env" 29 | "testdata/first.env" 30 | ]; 31 | log-driver = "journald"; 32 | autoStart = false; 33 | extraOptions = [ 34 | "--network-alias=first" 35 | "--network=test_default" 36 | ]; 37 | }; 38 | systemd.services."podman-test-first" = { 39 | serviceConfig = { 40 | Restart = lib.mkOverride 90 "no"; 41 | }; 42 | after = [ 43 | "podman-network-test_default.service" 44 | ]; 45 | requires = [ 46 | "podman-network-test_default.service" 47 | ]; 48 | }; 49 | virtualisation.oci-containers.containers."test-second" = { 50 | image = "nginx:latest"; 51 | environment = { 52 | "DEPTH" = "20"; 53 | "NAME" = "second"; 54 | }; 55 | environmentFiles = [ 56 | "testdata/first.env" 57 | "testdata/second.env" 58 | ]; 59 | log-driver = "journald"; 60 | autoStart = false; 61 | extraOptions = [ 62 | "--network-alias=second" 63 | "--network=test_default" 64 | ]; 65 | }; 66 | systemd.services."podman-test-second" = { 67 | serviceConfig = { 68 | Restart = lib.mkOverride 90 "no"; 69 | }; 70 | after = [ 71 | "podman-network-test_default.service" 72 | ]; 73 | requires = [ 74 | "podman-network-test_default.service" 75 | ]; 76 | }; 77 | virtualisation.oci-containers.containers."test-third" = { 78 | image = "nginx:latest"; 79 | environmentFiles = [ 80 | "testdata/first.env" 81 | "unknown.env" 82 | ]; 83 | log-driver = "journald"; 84 | autoStart = false; 85 | extraOptions = [ 86 | "--network-alias=third" 87 | "--network=test_default" 88 | ]; 89 | }; 90 | systemd.services."podman-test-third" = { 91 | serviceConfig = { 92 | Restart = lib.mkOverride 90 "no"; 93 | }; 94 | after = [ 95 | "podman-network-test_default.service" 96 | ]; 97 | requires = [ 98 | "podman-network-test_default.service" 99 | ]; 100 | }; 101 | 102 | # Networks 103 | systemd.services."podman-network-test_default" = { 104 | path = [ pkgs.podman ]; 105 | serviceConfig = { 106 | Type = "oneshot"; 107 | RemainAfterExit = true; 108 | ExecStop = "podman network rm -f test_default"; 109 | }; 110 | script = '' 111 | podman network inspect test_default || podman network create test_default 112 | ''; 113 | partOf = [ "podman-compose-test-root.target" ]; 114 | wantedBy = [ "podman-compose-test-root.target" ]; 115 | }; 116 | 117 | # Root service 118 | # When started, this will automatically create all resources and start 119 | # the containers. When stopped, this will teardown all resources. 120 | systemd.targets."podman-compose-test-root" = { 121 | unitConfig = { 122 | Description = "Root target generated by compose2nix."; 123 | }; 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /testdata/TestDeployDevices.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | test: 3 | image: nginx:latest 4 | security_opt: 5 | - label=disable 6 | devices: 7 | - nvidia.com/gpu=abc 8 | - source: /dev/abc 9 | target: /dev/def 10 | permissions: rw 11 | deploy: 12 | resources: 13 | reservations: 14 | devices: 15 | - driver: cdi 16 | device_ids: 17 | - nvidia.com/gpu=all 18 | capabilities: 19 | - gpu 20 | - driver: ignore-me 21 | device_ids: 22 | - unknown 23 | capabilities: 24 | - gpu 25 | restart: unless-stopped 26 | deploy-nvidia: 27 | image: nginx:latest 28 | deploy: 29 | resources: 30 | reservations: 31 | devices: 32 | - driver: nvidia 33 | count: 1 34 | capabilities: 35 | - gpu 36 | limits: 37 | cpus: '0.50' 38 | 39 | -------------------------------------------------------------------------------- /testdata/TestDeployDevices.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-deploy-nvidia" = { 13 | image = "nginx:latest"; 14 | log-driver = "journald"; 15 | autoStart = false; 16 | extraOptions = [ 17 | "--cpus=0.5" 18 | "--device=nvidia.com/gpu=all" 19 | "--network-alias=deploy-nvidia" 20 | "--network=test_default" 21 | ]; 22 | }; 23 | systemd.services."docker-test-deploy-nvidia" = { 24 | serviceConfig = { 25 | Restart = lib.mkOverride 90 "no"; 26 | }; 27 | after = [ 28 | "docker-network-test_default.service" 29 | ]; 30 | requires = [ 31 | "docker-network-test_default.service" 32 | ]; 33 | }; 34 | virtualisation.oci-containers.containers."test-test" = { 35 | image = "nginx:latest"; 36 | log-driver = "journald"; 37 | autoStart = false; 38 | extraOptions = [ 39 | "--device=/dev/abc:/dev/def:rw" 40 | "--device=nvidia.com/gpu=abc" 41 | "--device=nvidia.com/gpu=all" 42 | "--network-alias=test" 43 | "--network=test_default" 44 | "--security-opt=label=disable" 45 | ]; 46 | }; 47 | systemd.services."docker-test-test" = { 48 | serviceConfig = { 49 | Restart = lib.mkOverride 90 "always"; 50 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 51 | RestartSec = lib.mkOverride 90 "100ms"; 52 | RestartSteps = lib.mkOverride 90 9; 53 | }; 54 | after = [ 55 | "docker-network-test_default.service" 56 | ]; 57 | requires = [ 58 | "docker-network-test_default.service" 59 | ]; 60 | }; 61 | 62 | # Networks 63 | systemd.services."docker-network-test_default" = { 64 | path = [ pkgs.docker ]; 65 | serviceConfig = { 66 | Type = "oneshot"; 67 | RemainAfterExit = true; 68 | ExecStop = "docker network rm -f test_default"; 69 | }; 70 | script = '' 71 | docker network inspect test_default || docker network create test_default 72 | ''; 73 | partOf = [ "docker-compose-test-root.target" ]; 74 | wantedBy = [ "docker-compose-test-root.target" ]; 75 | }; 76 | 77 | # Root service 78 | # When started, this will automatically create all resources and start 79 | # the containers. When stopped, this will teardown all resources. 80 | systemd.targets."docker-compose-test-root" = { 81 | unitConfig = { 82 | Description = "Root target generated by compose2nix."; 83 | }; 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /testdata/TestDeployDevices.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-deploy-nvidia" = { 22 | image = "nginx:latest"; 23 | log-driver = "journald"; 24 | autoStart = false; 25 | extraOptions = [ 26 | "--cpus=0.5" 27 | "--device=nvidia.com/gpu=all" 28 | "--network-alias=deploy-nvidia" 29 | "--network=test_default" 30 | ]; 31 | }; 32 | systemd.services."podman-test-deploy-nvidia" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "no"; 35 | }; 36 | after = [ 37 | "podman-network-test_default.service" 38 | ]; 39 | requires = [ 40 | "podman-network-test_default.service" 41 | ]; 42 | }; 43 | virtualisation.oci-containers.containers."test-test" = { 44 | image = "nginx:latest"; 45 | log-driver = "journald"; 46 | autoStart = false; 47 | extraOptions = [ 48 | "--device=/dev/abc:/dev/def:rw" 49 | "--device=nvidia.com/gpu=abc" 50 | "--device=nvidia.com/gpu=all" 51 | "--network-alias=test" 52 | "--network=test_default" 53 | "--security-opt=label=disable" 54 | ]; 55 | }; 56 | systemd.services."podman-test-test" = { 57 | serviceConfig = { 58 | Restart = lib.mkOverride 90 "always"; 59 | }; 60 | after = [ 61 | "podman-network-test_default.service" 62 | ]; 63 | requires = [ 64 | "podman-network-test_default.service" 65 | ]; 66 | }; 67 | 68 | # Networks 69 | systemd.services."podman-network-test_default" = { 70 | path = [ pkgs.podman ]; 71 | serviceConfig = { 72 | Type = "oneshot"; 73 | RemainAfterExit = true; 74 | ExecStop = "podman network rm -f test_default"; 75 | }; 76 | script = '' 77 | podman network inspect test_default || podman network create test_default 78 | ''; 79 | partOf = [ "podman-compose-test-root.target" ]; 80 | wantedBy = [ "podman-compose-test-root.target" ]; 81 | }; 82 | 83 | # Root service 84 | # When started, this will automatically create all resources and start 85 | # the containers. When stopped, this will teardown all resources. 86 | systemd.targets."podman-compose-test-root" = { 87 | unitConfig = { 88 | Description = "Root target generated by compose2nix."; 89 | }; 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /testdata/TestEmptyEnv.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | service-a: 3 | image: nginx:latest 4 | environment: 5 | SOME_EMPTY_ENV_VAR: 6 | EMPTY_BUT_OVERRIDDEN_BY_ENV_FILE: 7 | NOT_EMPTY: "" 8 | service-b: 9 | image: nginx:latest 10 | environment: 11 | - ANOTHER_EMPTY_ENV_VAR 12 | - EMPTY_BUT_OVERRIDDEN_BY_ENV_FILE 13 | - ANOTHER_NOT_EMPTY= 14 | 15 | -------------------------------------------------------------------------------- /testdata/TestEmptyEnv.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-service-a" = { 13 | image = "nginx:latest"; 14 | environment = { 15 | "EMPTY_BUT_OVERRIDDEN_BY_ENV_FILE" = "abcde"; 16 | "NOT_EMPTY" = ""; 17 | }; 18 | log-driver = "journald"; 19 | autoStart = false; 20 | extraOptions = [ 21 | "--network-alias=service-a" 22 | "--network=test_default" 23 | ]; 24 | }; 25 | systemd.services."docker-test-service-a" = { 26 | serviceConfig = { 27 | Restart = lib.mkOverride 90 "no"; 28 | }; 29 | after = [ 30 | "docker-network-test_default.service" 31 | ]; 32 | requires = [ 33 | "docker-network-test_default.service" 34 | ]; 35 | }; 36 | virtualisation.oci-containers.containers."test-service-b" = { 37 | image = "nginx:latest"; 38 | environment = { 39 | "ANOTHER_NOT_EMPTY" = ""; 40 | "EMPTY_BUT_OVERRIDDEN_BY_ENV_FILE" = "abcde"; 41 | }; 42 | log-driver = "journald"; 43 | autoStart = false; 44 | extraOptions = [ 45 | "--network-alias=service-b" 46 | "--network=test_default" 47 | ]; 48 | }; 49 | systemd.services."docker-test-service-b" = { 50 | serviceConfig = { 51 | Restart = lib.mkOverride 90 "no"; 52 | }; 53 | after = [ 54 | "docker-network-test_default.service" 55 | ]; 56 | requires = [ 57 | "docker-network-test_default.service" 58 | ]; 59 | }; 60 | 61 | # Networks 62 | systemd.services."docker-network-test_default" = { 63 | path = [ pkgs.docker ]; 64 | serviceConfig = { 65 | Type = "oneshot"; 66 | RemainAfterExit = true; 67 | ExecStop = "docker network rm -f test_default"; 68 | }; 69 | script = '' 70 | docker network inspect test_default || docker network create test_default 71 | ''; 72 | partOf = [ "docker-compose-test-root.target" ]; 73 | wantedBy = [ "docker-compose-test-root.target" ]; 74 | }; 75 | 76 | # Root service 77 | # When started, this will automatically create all resources and start 78 | # the containers. When stopped, this will teardown all resources. 79 | systemd.targets."docker-compose-test-root" = { 80 | unitConfig = { 81 | Description = "Root target generated by compose2nix."; 82 | }; 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /testdata/TestEmptyEnv.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-service-a" = { 22 | image = "nginx:latest"; 23 | environment = { 24 | "EMPTY_BUT_OVERRIDDEN_BY_ENV_FILE" = "abcde"; 25 | "NOT_EMPTY" = ""; 26 | }; 27 | log-driver = "journald"; 28 | autoStart = false; 29 | extraOptions = [ 30 | "--network-alias=service-a" 31 | "--network=test_default" 32 | ]; 33 | }; 34 | systemd.services."podman-test-service-a" = { 35 | serviceConfig = { 36 | Restart = lib.mkOverride 90 "no"; 37 | }; 38 | after = [ 39 | "podman-network-test_default.service" 40 | ]; 41 | requires = [ 42 | "podman-network-test_default.service" 43 | ]; 44 | }; 45 | virtualisation.oci-containers.containers."test-service-b" = { 46 | image = "nginx:latest"; 47 | environment = { 48 | "ANOTHER_NOT_EMPTY" = ""; 49 | "EMPTY_BUT_OVERRIDDEN_BY_ENV_FILE" = "abcde"; 50 | }; 51 | log-driver = "journald"; 52 | autoStart = false; 53 | extraOptions = [ 54 | "--network-alias=service-b" 55 | "--network=test_default" 56 | ]; 57 | }; 58 | systemd.services."podman-test-service-b" = { 59 | serviceConfig = { 60 | Restart = lib.mkOverride 90 "no"; 61 | }; 62 | after = [ 63 | "podman-network-test_default.service" 64 | ]; 65 | requires = [ 66 | "podman-network-test_default.service" 67 | ]; 68 | }; 69 | 70 | # Networks 71 | systemd.services."podman-network-test_default" = { 72 | path = [ pkgs.podman ]; 73 | serviceConfig = { 74 | Type = "oneshot"; 75 | RemainAfterExit = true; 76 | ExecStop = "podman network rm -f test_default"; 77 | }; 78 | script = '' 79 | podman network inspect test_default || podman network create test_default 80 | ''; 81 | partOf = [ "podman-compose-test-root.target" ]; 82 | wantedBy = [ "podman-compose-test-root.target" ]; 83 | }; 84 | 85 | # Root service 86 | # When started, this will automatically create all resources and start 87 | # the containers. When stopped, this will teardown all resources. 88 | systemd.targets."podman-compose-test-root" = { 89 | unitConfig = { 90 | Description = "Root target generated by compose2nix."; 91 | }; 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /testdata/TestEnvFiles.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | first: 3 | image: nginx:latest 4 | environment: 5 | PUID: ${PUID} 6 | env_file: 7 | - ./testdata/input.env 8 | - ./testdata/first.env 9 | - path: ./override.env 10 | required: false 11 | second: 12 | image: nginx:latest 13 | env_file: 14 | - ./testdata/second.env 15 | -------------------------------------------------------------------------------- /testdata/TestEnvFiles.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-first" = { 13 | image = "nginx:latest"; 14 | environment = { 15 | "CLOUDFLARE_API_KEY" = "yomama"; 16 | "CLOUDFLARE_EMAIL" = "aaa@aaa.com"; 17 | "DEPTH" = "10"; 18 | "DOMAIN" = "hello.us"; 19 | "HOME_DOMAIN" = "hey.hello.us"; 20 | "NAME" = "first"; 21 | "PGID" = "1000"; 22 | "PUID" = "1000"; 23 | "THEMEPARK_THEME" = "potato"; 24 | "TIMEZONE" = "America/New_York"; 25 | }; 26 | environmentFiles = [ 27 | "override.env" 28 | "testdata/first.env" 29 | "testdata/input.env" 30 | ]; 31 | log-driver = "journald"; 32 | autoStart = false; 33 | extraOptions = [ 34 | "--network-alias=first" 35 | "--network=test_default" 36 | ]; 37 | }; 38 | systemd.services."docker-test-first" = { 39 | serviceConfig = { 40 | Restart = lib.mkOverride 90 "no"; 41 | }; 42 | after = [ 43 | "docker-network-test_default.service" 44 | ]; 45 | requires = [ 46 | "docker-network-test_default.service" 47 | ]; 48 | }; 49 | virtualisation.oci-containers.containers."test-second" = { 50 | image = "nginx:latest"; 51 | environment = { 52 | "DEPTH" = "20"; 53 | "NAME" = "second"; 54 | }; 55 | environmentFiles = [ 56 | "testdata/input.env" 57 | "testdata/second.env" 58 | ]; 59 | log-driver = "journald"; 60 | autoStart = false; 61 | extraOptions = [ 62 | "--network-alias=second" 63 | "--network=test_default" 64 | ]; 65 | }; 66 | systemd.services."docker-test-second" = { 67 | serviceConfig = { 68 | Restart = lib.mkOverride 90 "no"; 69 | }; 70 | after = [ 71 | "docker-network-test_default.service" 72 | ]; 73 | requires = [ 74 | "docker-network-test_default.service" 75 | ]; 76 | }; 77 | 78 | # Networks 79 | systemd.services."docker-network-test_default" = { 80 | path = [ pkgs.docker ]; 81 | serviceConfig = { 82 | Type = "oneshot"; 83 | RemainAfterExit = true; 84 | ExecStop = "docker network rm -f test_default"; 85 | }; 86 | script = '' 87 | docker network inspect test_default || docker network create test_default 88 | ''; 89 | partOf = [ "docker-compose-test-root.target" ]; 90 | wantedBy = [ "docker-compose-test-root.target" ]; 91 | }; 92 | 93 | # Root service 94 | # When started, this will automatically create all resources and start 95 | # the containers. When stopped, this will teardown all resources. 96 | systemd.targets."docker-compose-test-root" = { 97 | unitConfig = { 98 | Description = "Root target generated by compose2nix."; 99 | }; 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /testdata/TestEnvFiles.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-first" = { 22 | image = "nginx:latest"; 23 | environment = { 24 | "CLOUDFLARE_API_KEY" = "yomama"; 25 | "CLOUDFLARE_EMAIL" = "aaa@aaa.com"; 26 | "DEPTH" = "10"; 27 | "DOMAIN" = "hello.us"; 28 | "HOME_DOMAIN" = "hey.hello.us"; 29 | "NAME" = "first"; 30 | "PGID" = "1000"; 31 | "PUID" = "1000"; 32 | "THEMEPARK_THEME" = "potato"; 33 | "TIMEZONE" = "America/New_York"; 34 | }; 35 | environmentFiles = [ 36 | "override.env" 37 | "testdata/first.env" 38 | "testdata/input.env" 39 | ]; 40 | log-driver = "journald"; 41 | autoStart = false; 42 | extraOptions = [ 43 | "--network-alias=first" 44 | "--network=test_default" 45 | ]; 46 | }; 47 | systemd.services."podman-test-first" = { 48 | serviceConfig = { 49 | Restart = lib.mkOverride 90 "no"; 50 | }; 51 | after = [ 52 | "podman-network-test_default.service" 53 | ]; 54 | requires = [ 55 | "podman-network-test_default.service" 56 | ]; 57 | }; 58 | virtualisation.oci-containers.containers."test-second" = { 59 | image = "nginx:latest"; 60 | environment = { 61 | "DEPTH" = "20"; 62 | "NAME" = "second"; 63 | }; 64 | environmentFiles = [ 65 | "testdata/input.env" 66 | "testdata/second.env" 67 | ]; 68 | log-driver = "journald"; 69 | autoStart = false; 70 | extraOptions = [ 71 | "--network-alias=second" 72 | "--network=test_default" 73 | ]; 74 | }; 75 | systemd.services."podman-test-second" = { 76 | serviceConfig = { 77 | Restart = lib.mkOverride 90 "no"; 78 | }; 79 | after = [ 80 | "podman-network-test_default.service" 81 | ]; 82 | requires = [ 83 | "podman-network-test_default.service" 84 | ]; 85 | }; 86 | 87 | # Networks 88 | systemd.services."podman-network-test_default" = { 89 | path = [ pkgs.podman ]; 90 | serviceConfig = { 91 | Type = "oneshot"; 92 | RemainAfterExit = true; 93 | ExecStop = "podman network rm -f test_default"; 94 | }; 95 | script = '' 96 | podman network inspect test_default || podman network create test_default 97 | ''; 98 | partOf = [ "podman-compose-test-root.target" ]; 99 | wantedBy = [ "podman-compose-test-root.target" ]; 100 | }; 101 | 102 | # Root service 103 | # When started, this will automatically create all resources and start 104 | # the containers. When stopped, this will teardown all resources. 105 | systemd.targets."podman-compose-test-root" = { 106 | unitConfig = { 107 | Description = "Root target generated by compose2nix."; 108 | }; 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /testdata/TestEnvFilesOnly.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | first: 3 | image: nginx:latest 4 | environment: 5 | PUID: ${PUID} 6 | env_file: 7 | - ./testdata/input.env 8 | - ./testdata/first.env 9 | - path: ./override.env 10 | required: false 11 | second: 12 | image: nginx:latest 13 | env_file: 14 | - ./testdata/second.env 15 | -------------------------------------------------------------------------------- /testdata/TestEnvFilesOnly.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-first" = { 13 | image = "nginx:latest"; 14 | environmentFiles = [ 15 | "override.env" 16 | "testdata/first.env" 17 | "testdata/input.env" 18 | ]; 19 | log-driver = "journald"; 20 | autoStart = false; 21 | extraOptions = [ 22 | "--network-alias=first" 23 | "--network=test_default" 24 | ]; 25 | }; 26 | systemd.services."docker-test-first" = { 27 | serviceConfig = { 28 | Restart = lib.mkOverride 90 "no"; 29 | }; 30 | after = [ 31 | "docker-network-test_default.service" 32 | ]; 33 | requires = [ 34 | "docker-network-test_default.service" 35 | ]; 36 | }; 37 | virtualisation.oci-containers.containers."test-second" = { 38 | image = "nginx:latest"; 39 | environmentFiles = [ 40 | "testdata/input.env" 41 | "testdata/second.env" 42 | ]; 43 | log-driver = "journald"; 44 | autoStart = false; 45 | extraOptions = [ 46 | "--network-alias=second" 47 | "--network=test_default" 48 | ]; 49 | }; 50 | systemd.services."docker-test-second" = { 51 | serviceConfig = { 52 | Restart = lib.mkOverride 90 "no"; 53 | }; 54 | after = [ 55 | "docker-network-test_default.service" 56 | ]; 57 | requires = [ 58 | "docker-network-test_default.service" 59 | ]; 60 | }; 61 | 62 | # Networks 63 | systemd.services."docker-network-test_default" = { 64 | path = [ pkgs.docker ]; 65 | serviceConfig = { 66 | Type = "oneshot"; 67 | RemainAfterExit = true; 68 | ExecStop = "docker network rm -f test_default"; 69 | }; 70 | script = '' 71 | docker network inspect test_default || docker network create test_default 72 | ''; 73 | partOf = [ "docker-compose-test-root.target" ]; 74 | wantedBy = [ "docker-compose-test-root.target" ]; 75 | }; 76 | 77 | # Root service 78 | # When started, this will automatically create all resources and start 79 | # the containers. When stopped, this will teardown all resources. 80 | systemd.targets."docker-compose-test-root" = { 81 | unitConfig = { 82 | Description = "Root target generated by compose2nix."; 83 | }; 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /testdata/TestEnvFilesOnly.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-first" = { 22 | image = "nginx:latest"; 23 | environmentFiles = [ 24 | "override.env" 25 | "testdata/first.env" 26 | "testdata/input.env" 27 | ]; 28 | log-driver = "journald"; 29 | autoStart = false; 30 | extraOptions = [ 31 | "--network-alias=first" 32 | "--network=test_default" 33 | ]; 34 | }; 35 | systemd.services."podman-test-first" = { 36 | serviceConfig = { 37 | Restart = lib.mkOverride 90 "no"; 38 | }; 39 | after = [ 40 | "podman-network-test_default.service" 41 | ]; 42 | requires = [ 43 | "podman-network-test_default.service" 44 | ]; 45 | }; 46 | virtualisation.oci-containers.containers."test-second" = { 47 | image = "nginx:latest"; 48 | environmentFiles = [ 49 | "testdata/input.env" 50 | "testdata/second.env" 51 | ]; 52 | log-driver = "journald"; 53 | autoStart = false; 54 | extraOptions = [ 55 | "--network-alias=second" 56 | "--network=test_default" 57 | ]; 58 | }; 59 | systemd.services."podman-test-second" = { 60 | serviceConfig = { 61 | Restart = lib.mkOverride 90 "no"; 62 | }; 63 | after = [ 64 | "podman-network-test_default.service" 65 | ]; 66 | requires = [ 67 | "podman-network-test_default.service" 68 | ]; 69 | }; 70 | 71 | # Networks 72 | systemd.services."podman-network-test_default" = { 73 | path = [ pkgs.podman ]; 74 | serviceConfig = { 75 | Type = "oneshot"; 76 | RemainAfterExit = true; 77 | ExecStop = "podman network rm -f test_default"; 78 | }; 79 | script = '' 80 | podman network inspect test_default || podman network create test_default 81 | ''; 82 | partOf = [ "podman-compose-test-root.target" ]; 83 | wantedBy = [ "podman-compose-test-root.target" ]; 84 | }; 85 | 86 | # Root service 87 | # When started, this will automatically create all resources and start 88 | # the containers. When stopped, this will teardown all resources. 89 | systemd.targets."podman-compose-test-root" = { 90 | unitConfig = { 91 | Description = "Root target generated by compose2nix."; 92 | }; 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /testdata/TestEscapeChars.compose.yml: -------------------------------------------------------------------------------- 1 | name: "dovecot" 2 | services: 3 | dovecot: 4 | container_name: dovecot 5 | image: dovecot 6 | labels: 7 | ofelia.enabled: "true" 8 | ofelia.job-exec.dovecot_imapsync_runner.schedule: "@every 1m" 9 | ofelia.job-exec.dovecot_imapsync_runner.no-overlap: "true" 10 | ofelia.job-exec.dovecot_imapsync_runner.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.pl || exit 0\"" 11 | ofelia.job-exec.dovecot_trim_logs.schedule: "@every 1m" 12 | ofelia.job-exec.dovecot_trim_logs.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\"" 13 | networks: 14 | - abc 15 | volumes: 16 | - def:/path/to/path 17 | 18 | networks: 19 | abc: 20 | labels: 21 | my-label: "\"some quoted string\"" 22 | 23 | volumes: 24 | def: 25 | labels: 26 | other-label: "\"another quota string\"" 27 | -------------------------------------------------------------------------------- /testdata/TestEscapeChars.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."dovecot" = { 13 | image = "dovecot"; 14 | volumes = [ 15 | "dovecot_def:/path/to/path:rw" 16 | ]; 17 | labels = { 18 | "ofelia.enabled" = "true"; 19 | "ofelia.job-exec.dovecot_imapsync_runner.command" = "/bin/bash -c \"[[ \${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.pl || exit 0\""; 20 | "ofelia.job-exec.dovecot_imapsync_runner.no-overlap" = "true"; 21 | "ofelia.job-exec.dovecot_imapsync_runner.schedule" = "@every 1m"; 22 | "ofelia.job-exec.dovecot_trim_logs.command" = "/bin/bash -c \"[[ \${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\""; 23 | "ofelia.job-exec.dovecot_trim_logs.schedule" = "@every 1m"; 24 | }; 25 | log-driver = "journald"; 26 | autoStart = false; 27 | extraOptions = [ 28 | "--network-alias=dovecot" 29 | "--network=dovecot_abc" 30 | ]; 31 | }; 32 | systemd.services."docker-dovecot" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "no"; 35 | }; 36 | after = [ 37 | "docker-network-dovecot_abc.service" 38 | "docker-volume-dovecot_def.service" 39 | ]; 40 | requires = [ 41 | "docker-network-dovecot_abc.service" 42 | "docker-volume-dovecot_def.service" 43 | ]; 44 | }; 45 | 46 | # Networks 47 | systemd.services."docker-network-dovecot_abc" = { 48 | path = [ pkgs.docker ]; 49 | serviceConfig = { 50 | Type = "oneshot"; 51 | RemainAfterExit = true; 52 | ExecStop = "docker network rm -f dovecot_abc"; 53 | }; 54 | script = '' 55 | docker network inspect dovecot_abc || docker network create dovecot_abc --label=my-label="some quoted string" 56 | ''; 57 | partOf = [ "docker-compose-dovecot-root.target" ]; 58 | wantedBy = [ "docker-compose-dovecot-root.target" ]; 59 | }; 60 | 61 | # Volumes 62 | systemd.services."docker-volume-dovecot_def" = { 63 | path = [ pkgs.docker ]; 64 | serviceConfig = { 65 | Type = "oneshot"; 66 | RemainAfterExit = true; 67 | }; 68 | script = '' 69 | docker volume inspect dovecot_def || docker volume create dovecot_def --label=other-label="another quota string" 70 | ''; 71 | partOf = [ "docker-compose-dovecot-root.target" ]; 72 | wantedBy = [ "docker-compose-dovecot-root.target" ]; 73 | }; 74 | 75 | # Root service 76 | # When started, this will automatically create all resources and start 77 | # the containers. When stopped, this will teardown all resources. 78 | systemd.targets."docker-compose-dovecot-root" = { 79 | unitConfig = { 80 | Description = "Root target generated by compose2nix."; 81 | }; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /testdata/TestEscapeChars.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."dovecot" = { 22 | image = "dovecot"; 23 | volumes = [ 24 | "dovecot_def:/path/to/path:rw" 25 | ]; 26 | labels = { 27 | "ofelia.enabled" = "true"; 28 | "ofelia.job-exec.dovecot_imapsync_runner.command" = "/bin/bash -c \"[[ \${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.pl || exit 0\""; 29 | "ofelia.job-exec.dovecot_imapsync_runner.no-overlap" = "true"; 30 | "ofelia.job-exec.dovecot_imapsync_runner.schedule" = "@every 1m"; 31 | "ofelia.job-exec.dovecot_trim_logs.command" = "/bin/bash -c \"[[ \${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\""; 32 | "ofelia.job-exec.dovecot_trim_logs.schedule" = "@every 1m"; 33 | }; 34 | log-driver = "journald"; 35 | autoStart = false; 36 | extraOptions = [ 37 | "--network-alias=dovecot" 38 | "--network=dovecot_abc" 39 | ]; 40 | }; 41 | systemd.services."podman-dovecot" = { 42 | serviceConfig = { 43 | Restart = lib.mkOverride 90 "no"; 44 | }; 45 | after = [ 46 | "podman-network-dovecot_abc.service" 47 | "podman-volume-dovecot_def.service" 48 | ]; 49 | requires = [ 50 | "podman-network-dovecot_abc.service" 51 | "podman-volume-dovecot_def.service" 52 | ]; 53 | }; 54 | 55 | # Networks 56 | systemd.services."podman-network-dovecot_abc" = { 57 | path = [ pkgs.podman ]; 58 | serviceConfig = { 59 | Type = "oneshot"; 60 | RemainAfterExit = true; 61 | ExecStop = "podman network rm -f dovecot_abc"; 62 | }; 63 | script = '' 64 | podman network inspect dovecot_abc || podman network create dovecot_abc --label=my-label="some quoted string" 65 | ''; 66 | partOf = [ "podman-compose-dovecot-root.target" ]; 67 | wantedBy = [ "podman-compose-dovecot-root.target" ]; 68 | }; 69 | 70 | # Volumes 71 | systemd.services."podman-volume-dovecot_def" = { 72 | path = [ pkgs.podman ]; 73 | serviceConfig = { 74 | Type = "oneshot"; 75 | RemainAfterExit = true; 76 | }; 77 | script = '' 78 | podman volume inspect dovecot_def || podman volume create dovecot_def --label=other-label="another quota string" 79 | ''; 80 | partOf = [ "podman-compose-dovecot-root.target" ]; 81 | wantedBy = [ "podman-compose-dovecot-root.target" ]; 82 | }; 83 | 84 | # Root service 85 | # When started, this will automatically create all resources and start 86 | # the containers. When stopped, this will teardown all resources. 87 | systemd.targets."podman-compose-dovecot-root" = { 88 | unitConfig = { 89 | Description = "Root target generated by compose2nix."; 90 | }; 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /testdata/TestExternalNetworksAndVolumes.compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | traefik: 4 | container_name: traefik 5 | image: docker.io/library/traefik 6 | ports: 7 | - "80:80" 8 | - "443:443" 9 | volumes: 10 | - test1:/test1 11 | - test2:/test2 12 | networks: 13 | test1: 14 | aliases: 15 | - my-container 16 | test2: 17 | test3: 18 | restart: unless-stopped 19 | 20 | networks: 21 | test1: 22 | test2: 23 | external: true 24 | test3: 25 | external: true 26 | 27 | volumes: 28 | test1: 29 | test2: 30 | external: true 31 | 32 | -------------------------------------------------------------------------------- /testdata/TestExternalNetworksAndVolumes.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."traefik" = { 13 | image = "docker.io/library/traefik"; 14 | volumes = [ 15 | "myproject_test1:/test1:rw" 16 | "test2:/test2:rw" 17 | ]; 18 | ports = [ 19 | "80:80/tcp" 20 | "443:443/tcp" 21 | ]; 22 | log-driver = "journald"; 23 | autoStart = false; 24 | extraOptions = [ 25 | "--network-alias=my-container" 26 | "--network-alias=traefik" 27 | "--network=myproject_test1" 28 | "--network=test2" 29 | "--network=test3" 30 | ]; 31 | }; 32 | systemd.services."docker-traefik" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "always"; 35 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 36 | RestartSec = lib.mkOverride 90 "100ms"; 37 | RestartSteps = lib.mkOverride 90 9; 38 | }; 39 | after = [ 40 | "docker-network-myproject_test1.service" 41 | "docker-volume-myproject_test1.service" 42 | ]; 43 | requires = [ 44 | "docker-network-myproject_test1.service" 45 | "docker-volume-myproject_test1.service" 46 | ]; 47 | }; 48 | 49 | # Networks 50 | systemd.services."docker-network-myproject_test1" = { 51 | path = [ pkgs.docker ]; 52 | serviceConfig = { 53 | Type = "oneshot"; 54 | RemainAfterExit = true; 55 | ExecStop = "docker network rm -f myproject_test1"; 56 | }; 57 | script = '' 58 | docker network inspect myproject_test1 || docker network create myproject_test1 59 | ''; 60 | partOf = [ "docker-compose-myproject-root.target" ]; 61 | wantedBy = [ "docker-compose-myproject-root.target" ]; 62 | }; 63 | 64 | # Volumes 65 | systemd.services."docker-volume-myproject_test1" = { 66 | path = [ pkgs.docker ]; 67 | serviceConfig = { 68 | Type = "oneshot"; 69 | RemainAfterExit = true; 70 | }; 71 | script = '' 72 | docker volume inspect myproject_test1 || docker volume create myproject_test1 73 | ''; 74 | partOf = [ "docker-compose-myproject-root.target" ]; 75 | wantedBy = [ "docker-compose-myproject-root.target" ]; 76 | }; 77 | 78 | # Root service 79 | # When started, this will automatically create all resources and start 80 | # the containers. When stopped, this will teardown all resources. 81 | systemd.targets."docker-compose-myproject-root" = { 82 | unitConfig = { 83 | Description = "Root target generated by compose2nix."; 84 | }; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /testdata/TestExternalNetworksAndVolumes.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."traefik" = { 22 | image = "docker.io/library/traefik"; 23 | volumes = [ 24 | "myproject_test1:/test1:rw" 25 | "test2:/test2:rw" 26 | ]; 27 | ports = [ 28 | "80:80/tcp" 29 | "443:443/tcp" 30 | ]; 31 | log-driver = "journald"; 32 | autoStart = false; 33 | extraOptions = [ 34 | "--network-alias=traefik" 35 | "--network=myproject_test1:alias=my-container" 36 | "--network=test2" 37 | "--network=test3" 38 | ]; 39 | }; 40 | systemd.services."podman-traefik" = { 41 | serviceConfig = { 42 | Restart = lib.mkOverride 90 "always"; 43 | }; 44 | after = [ 45 | "podman-network-myproject_test1.service" 46 | "podman-volume-myproject_test1.service" 47 | ]; 48 | requires = [ 49 | "podman-network-myproject_test1.service" 50 | "podman-volume-myproject_test1.service" 51 | ]; 52 | }; 53 | 54 | # Networks 55 | systemd.services."podman-network-myproject_test1" = { 56 | path = [ pkgs.podman ]; 57 | serviceConfig = { 58 | Type = "oneshot"; 59 | RemainAfterExit = true; 60 | ExecStop = "podman network rm -f myproject_test1"; 61 | }; 62 | script = '' 63 | podman network inspect myproject_test1 || podman network create myproject_test1 64 | ''; 65 | partOf = [ "podman-compose-myproject-root.target" ]; 66 | wantedBy = [ "podman-compose-myproject-root.target" ]; 67 | }; 68 | 69 | # Volumes 70 | systemd.services."podman-volume-myproject_test1" = { 71 | path = [ pkgs.podman ]; 72 | serviceConfig = { 73 | Type = "oneshot"; 74 | RemainAfterExit = true; 75 | }; 76 | script = '' 77 | podman volume inspect myproject_test1 || podman volume create myproject_test1 78 | ''; 79 | partOf = [ "podman-compose-myproject-root.target" ]; 80 | wantedBy = [ "podman-compose-myproject-root.target" ]; 81 | }; 82 | 83 | # Root service 84 | # When started, this will automatically create all resources and start 85 | # the containers. When stopped, this will teardown all resources. 86 | systemd.targets."podman-compose-myproject-root" = { 87 | unitConfig = { 88 | Description = "Root target generated by compose2nix."; 89 | }; 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /testdata/TestMacvlanSupport.compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | teddycloud: 4 | container_name: container 5 | mac_address: 10:50:02:01:00:02 6 | dns: 192.168.8.1 7 | hostname: tc 8 | image: ghcr.io/container/container:latest 9 | ports: 10 | - 80:80 11 | - 443:443 12 | volumes: 13 | - /opt/container/certs:/container/certs 14 | restart: unless-stopped 15 | networks: 16 | homenet: 17 | ipv4_address: 192.168.8.10 18 | 19 | networks: 20 | homenet: 21 | driver: macvlan 22 | driver_opts: 23 | parent: enp2s0 24 | ipam: 25 | config: 26 | - subnet: 192.168.8.0/24 27 | gateway: 192.168.8.1 28 | aux_addresses: 29 | host1: 192.168.8.5 30 | 31 | -------------------------------------------------------------------------------- /testdata/TestMacvlanSupport.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."container" = { 13 | image = "ghcr.io/container/container:latest"; 14 | volumes = [ 15 | "/opt/container/certs:/container/certs:rw" 16 | ]; 17 | ports = [ 18 | "80:80/tcp" 19 | "443:443/tcp" 20 | ]; 21 | log-driver = "journald"; 22 | autoStart = false; 23 | extraOptions = [ 24 | "--dns=192.168.8.1" 25 | "--hostname=tc" 26 | "--ip=192.168.8.10" 27 | "--mac-address=10:50:02:01:00:02" 28 | "--network-alias=teddycloud" 29 | "--network=myproject_homenet" 30 | ]; 31 | }; 32 | systemd.services."docker-container" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "always"; 35 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 36 | RestartSec = lib.mkOverride 90 "100ms"; 37 | RestartSteps = lib.mkOverride 90 9; 38 | }; 39 | after = [ 40 | "docker-network-myproject_homenet.service" 41 | ]; 42 | requires = [ 43 | "docker-network-myproject_homenet.service" 44 | ]; 45 | }; 46 | 47 | # Networks 48 | systemd.services."docker-network-myproject_homenet" = { 49 | path = [ pkgs.docker ]; 50 | serviceConfig = { 51 | Type = "oneshot"; 52 | RemainAfterExit = true; 53 | ExecStop = "docker network rm -f myproject_homenet"; 54 | }; 55 | script = '' 56 | docker network inspect myproject_homenet || docker network create myproject_homenet --driver=macvlan --opt=parent=enp2s0 --subnet=192.168.8.0/24 --gateway=192.168.8.1 --aux-address="host1=192.168.8.5" 57 | ''; 58 | partOf = [ "docker-compose-myproject-root.target" ]; 59 | wantedBy = [ "docker-compose-myproject-root.target" ]; 60 | }; 61 | 62 | # Root service 63 | # When started, this will automatically create all resources and start 64 | # the containers. When stopped, this will teardown all resources. 65 | systemd.targets."docker-compose-myproject-root" = { 66 | unitConfig = { 67 | Description = "Root target generated by compose2nix."; 68 | }; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /testdata/TestMacvlanSupport.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."container" = { 22 | image = "ghcr.io/container/container:latest"; 23 | volumes = [ 24 | "/opt/container/certs:/container/certs:rw" 25 | ]; 26 | ports = [ 27 | "80:80/tcp" 28 | "443:443/tcp" 29 | ]; 30 | log-driver = "journald"; 31 | autoStart = false; 32 | extraOptions = [ 33 | "--dns=192.168.8.1" 34 | "--hostname=tc" 35 | "--ip=192.168.8.10" 36 | "--mac-address=10:50:02:01:00:02" 37 | "--network-alias=teddycloud" 38 | "--network=myproject_homenet" 39 | ]; 40 | }; 41 | systemd.services."podman-container" = { 42 | serviceConfig = { 43 | Restart = lib.mkOverride 90 "always"; 44 | }; 45 | after = [ 46 | "podman-network-myproject_homenet.service" 47 | ]; 48 | requires = [ 49 | "podman-network-myproject_homenet.service" 50 | ]; 51 | }; 52 | 53 | # Networks 54 | systemd.services."podman-network-myproject_homenet" = { 55 | path = [ pkgs.podman ]; 56 | serviceConfig = { 57 | Type = "oneshot"; 58 | RemainAfterExit = true; 59 | ExecStop = "podman network rm -f myproject_homenet"; 60 | }; 61 | script = '' 62 | podman network inspect myproject_homenet || podman network create myproject_homenet --driver=macvlan --opt=parent=enp2s0 --subnet=192.168.8.0/24 --gateway=192.168.8.1 63 | ''; 64 | partOf = [ "podman-compose-myproject-root.target" ]; 65 | wantedBy = [ "podman-compose-myproject-root.target" ]; 66 | }; 67 | 68 | # Root service 69 | # When started, this will automatically create all resources and start 70 | # the containers. When stopped, this will teardown all resources. 71 | systemd.targets."podman-compose-myproject-root" = { 72 | unitConfig = { 73 | Description = "Root target generated by compose2nix."; 74 | }; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /testdata/TestMultipleNetworks.compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | traefik: 4 | container_name: traefik 5 | image: docker.io/library/traefik 6 | ports: 7 | - "80:80" 8 | - "443:443" 9 | networks: 10 | test1: 11 | aliases: 12 | - my-container 13 | test2: 14 | test3: 15 | restart: unless-stopped 16 | 17 | networks: 18 | test1: 19 | internal: true 20 | test2: 21 | test3: 22 | 23 | -------------------------------------------------------------------------------- /testdata/TestMultipleNetworks.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."traefik" = { 13 | image = "docker.io/library/traefik"; 14 | ports = [ 15 | "80:80/tcp" 16 | "443:443/tcp" 17 | ]; 18 | log-driver = "journald"; 19 | autoStart = false; 20 | extraOptions = [ 21 | "--network-alias=my-container" 22 | "--network-alias=traefik" 23 | "--network=myproject_test1" 24 | "--network=myproject_test2" 25 | "--network=myproject_test3" 26 | ]; 27 | }; 28 | systemd.services."docker-traefik" = { 29 | serviceConfig = { 30 | Restart = lib.mkOverride 90 "always"; 31 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 32 | RestartSec = lib.mkOverride 90 "100ms"; 33 | RestartSteps = lib.mkOverride 90 9; 34 | }; 35 | after = [ 36 | "docker-network-myproject_test1.service" 37 | "docker-network-myproject_test2.service" 38 | "docker-network-myproject_test3.service" 39 | ]; 40 | requires = [ 41 | "docker-network-myproject_test1.service" 42 | "docker-network-myproject_test2.service" 43 | "docker-network-myproject_test3.service" 44 | ]; 45 | }; 46 | 47 | # Networks 48 | systemd.services."docker-network-myproject_test1" = { 49 | path = [ pkgs.docker ]; 50 | serviceConfig = { 51 | Type = "oneshot"; 52 | RemainAfterExit = true; 53 | ExecStop = "docker network rm -f myproject_test1"; 54 | }; 55 | script = '' 56 | docker network inspect myproject_test1 || docker network create myproject_test1 --internal 57 | ''; 58 | partOf = [ "docker-compose-myproject-root.target" ]; 59 | wantedBy = [ "docker-compose-myproject-root.target" ]; 60 | }; 61 | systemd.services."docker-network-myproject_test2" = { 62 | path = [ pkgs.docker ]; 63 | serviceConfig = { 64 | Type = "oneshot"; 65 | RemainAfterExit = true; 66 | ExecStop = "docker network rm -f myproject_test2"; 67 | }; 68 | script = '' 69 | docker network inspect myproject_test2 || docker network create myproject_test2 70 | ''; 71 | partOf = [ "docker-compose-myproject-root.target" ]; 72 | wantedBy = [ "docker-compose-myproject-root.target" ]; 73 | }; 74 | systemd.services."docker-network-myproject_test3" = { 75 | path = [ pkgs.docker ]; 76 | serviceConfig = { 77 | Type = "oneshot"; 78 | RemainAfterExit = true; 79 | ExecStop = "docker network rm -f myproject_test3"; 80 | }; 81 | script = '' 82 | docker network inspect myproject_test3 || docker network create myproject_test3 83 | ''; 84 | partOf = [ "docker-compose-myproject-root.target" ]; 85 | wantedBy = [ "docker-compose-myproject-root.target" ]; 86 | }; 87 | 88 | # Root service 89 | # When started, this will automatically create all resources and start 90 | # the containers. When stopped, this will teardown all resources. 91 | systemd.targets."docker-compose-myproject-root" = { 92 | unitConfig = { 93 | Description = "Root target generated by compose2nix."; 94 | }; 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /testdata/TestMultipleNetworks.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."traefik" = { 22 | image = "docker.io/library/traefik"; 23 | ports = [ 24 | "80:80/tcp" 25 | "443:443/tcp" 26 | ]; 27 | log-driver = "journald"; 28 | autoStart = false; 29 | extraOptions = [ 30 | "--network-alias=traefik" 31 | "--network=myproject_test1:alias=my-container" 32 | "--network=myproject_test2" 33 | "--network=myproject_test3" 34 | ]; 35 | }; 36 | systemd.services."podman-traefik" = { 37 | serviceConfig = { 38 | Restart = lib.mkOverride 90 "always"; 39 | }; 40 | after = [ 41 | "podman-network-myproject_test1.service" 42 | "podman-network-myproject_test2.service" 43 | "podman-network-myproject_test3.service" 44 | ]; 45 | requires = [ 46 | "podman-network-myproject_test1.service" 47 | "podman-network-myproject_test2.service" 48 | "podman-network-myproject_test3.service" 49 | ]; 50 | }; 51 | 52 | # Networks 53 | systemd.services."podman-network-myproject_test1" = { 54 | path = [ pkgs.podman ]; 55 | serviceConfig = { 56 | Type = "oneshot"; 57 | RemainAfterExit = true; 58 | ExecStop = "podman network rm -f myproject_test1"; 59 | }; 60 | script = '' 61 | podman network inspect myproject_test1 || podman network create myproject_test1 --internal 62 | ''; 63 | partOf = [ "podman-compose-myproject-root.target" ]; 64 | wantedBy = [ "podman-compose-myproject-root.target" ]; 65 | }; 66 | systemd.services."podman-network-myproject_test2" = { 67 | path = [ pkgs.podman ]; 68 | serviceConfig = { 69 | Type = "oneshot"; 70 | RemainAfterExit = true; 71 | ExecStop = "podman network rm -f myproject_test2"; 72 | }; 73 | script = '' 74 | podman network inspect myproject_test2 || podman network create myproject_test2 75 | ''; 76 | partOf = [ "podman-compose-myproject-root.target" ]; 77 | wantedBy = [ "podman-compose-myproject-root.target" ]; 78 | }; 79 | systemd.services."podman-network-myproject_test3" = { 80 | path = [ pkgs.podman ]; 81 | serviceConfig = { 82 | Type = "oneshot"; 83 | RemainAfterExit = true; 84 | ExecStop = "podman network rm -f myproject_test3"; 85 | }; 86 | script = '' 87 | podman network inspect myproject_test3 || podman network create myproject_test3 88 | ''; 89 | partOf = [ "podman-compose-myproject-root.target" ]; 90 | wantedBy = [ "podman-compose-myproject-root.target" ]; 91 | }; 92 | 93 | # Root service 94 | # When started, this will automatically create all resources and start 95 | # the containers. When stopped, this will teardown all resources. 96 | systemd.targets."podman-compose-myproject-root" = { 97 | unitConfig = { 98 | Description = "Root target generated by compose2nix."; 99 | }; 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /testdata/TestNetworkAndVolumeNames.compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | traefik: 4 | container_name: traefik 5 | image: docker.io/library/traefik 6 | ports: 7 | - "80:80" 8 | - "443:443" 9 | networks: 10 | test1: 11 | test2: 12 | test3: 13 | volumes: 14 | - test1:/test1 15 | - test2:/test2 16 | - test3:/test3 17 | restart: unless-stopped 18 | 19 | networks: 20 | test1: 21 | internal: true 22 | test2: 23 | name: my-network 24 | test3: 25 | external: true 26 | 27 | volumes: 28 | test1: 29 | external: true 30 | test2: 31 | name: my-volume 32 | test3: 33 | 34 | -------------------------------------------------------------------------------- /testdata/TestNetworkAndVolumeNames.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."traefik" = { 13 | image = "docker.io/library/traefik"; 14 | volumes = [ 15 | "my-volume:/test2:rw" 16 | "myproject_test3:/test3:rw" 17 | "test1:/test1:rw" 18 | ]; 19 | ports = [ 20 | "80:80/tcp" 21 | "443:443/tcp" 22 | ]; 23 | log-driver = "journald"; 24 | autoStart = false; 25 | extraOptions = [ 26 | "--network-alias=traefik" 27 | "--network=my-network" 28 | "--network=myproject_test1" 29 | "--network=test3" 30 | ]; 31 | }; 32 | systemd.services."docker-traefik" = { 33 | serviceConfig = { 34 | Restart = lib.mkOverride 90 "always"; 35 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 36 | RestartSec = lib.mkOverride 90 "100ms"; 37 | RestartSteps = lib.mkOverride 90 9; 38 | }; 39 | after = [ 40 | "docker-network-my-network.service" 41 | "docker-network-myproject_test1.service" 42 | "docker-volume-my-volume.service" 43 | "docker-volume-myproject_test3.service" 44 | ]; 45 | requires = [ 46 | "docker-network-my-network.service" 47 | "docker-network-myproject_test1.service" 48 | "docker-volume-my-volume.service" 49 | "docker-volume-myproject_test3.service" 50 | ]; 51 | }; 52 | 53 | # Networks 54 | systemd.services."docker-network-my-network" = { 55 | path = [ pkgs.docker ]; 56 | serviceConfig = { 57 | Type = "oneshot"; 58 | RemainAfterExit = true; 59 | ExecStop = "docker network rm -f my-network"; 60 | }; 61 | script = '' 62 | docker network inspect my-network || docker network create my-network 63 | ''; 64 | partOf = [ "docker-compose-myproject-root.target" ]; 65 | wantedBy = [ "docker-compose-myproject-root.target" ]; 66 | }; 67 | systemd.services."docker-network-myproject_test1" = { 68 | path = [ pkgs.docker ]; 69 | serviceConfig = { 70 | Type = "oneshot"; 71 | RemainAfterExit = true; 72 | ExecStop = "docker network rm -f myproject_test1"; 73 | }; 74 | script = '' 75 | docker network inspect myproject_test1 || docker network create myproject_test1 --internal 76 | ''; 77 | partOf = [ "docker-compose-myproject-root.target" ]; 78 | wantedBy = [ "docker-compose-myproject-root.target" ]; 79 | }; 80 | 81 | # Volumes 82 | systemd.services."docker-volume-my-volume" = { 83 | path = [ pkgs.docker ]; 84 | serviceConfig = { 85 | Type = "oneshot"; 86 | RemainAfterExit = true; 87 | }; 88 | script = '' 89 | docker volume inspect my-volume || docker volume create my-volume 90 | ''; 91 | partOf = [ "docker-compose-myproject-root.target" ]; 92 | wantedBy = [ "docker-compose-myproject-root.target" ]; 93 | }; 94 | systemd.services."docker-volume-myproject_test3" = { 95 | path = [ pkgs.docker ]; 96 | serviceConfig = { 97 | Type = "oneshot"; 98 | RemainAfterExit = true; 99 | }; 100 | script = '' 101 | docker volume inspect myproject_test3 || docker volume create myproject_test3 102 | ''; 103 | partOf = [ "docker-compose-myproject-root.target" ]; 104 | wantedBy = [ "docker-compose-myproject-root.target" ]; 105 | }; 106 | 107 | # Root service 108 | # When started, this will automatically create all resources and start 109 | # the containers. When stopped, this will teardown all resources. 110 | systemd.targets."docker-compose-myproject-root" = { 111 | unitConfig = { 112 | Description = "Root target generated by compose2nix."; 113 | }; 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /testdata/TestNetworkAndVolumeNames.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."traefik" = { 22 | image = "docker.io/library/traefik"; 23 | volumes = [ 24 | "my-volume:/test2:rw" 25 | "myproject_test3:/test3:rw" 26 | "test1:/test1:rw" 27 | ]; 28 | ports = [ 29 | "80:80/tcp" 30 | "443:443/tcp" 31 | ]; 32 | log-driver = "journald"; 33 | autoStart = false; 34 | extraOptions = [ 35 | "--network-alias=traefik" 36 | "--network=my-network" 37 | "--network=myproject_test1" 38 | "--network=test3" 39 | ]; 40 | }; 41 | systemd.services."podman-traefik" = { 42 | serviceConfig = { 43 | Restart = lib.mkOverride 90 "always"; 44 | }; 45 | after = [ 46 | "podman-network-my-network.service" 47 | "podman-network-myproject_test1.service" 48 | "podman-volume-my-volume.service" 49 | "podman-volume-myproject_test3.service" 50 | ]; 51 | requires = [ 52 | "podman-network-my-network.service" 53 | "podman-network-myproject_test1.service" 54 | "podman-volume-my-volume.service" 55 | "podman-volume-myproject_test3.service" 56 | ]; 57 | }; 58 | 59 | # Networks 60 | systemd.services."podman-network-my-network" = { 61 | path = [ pkgs.podman ]; 62 | serviceConfig = { 63 | Type = "oneshot"; 64 | RemainAfterExit = true; 65 | ExecStop = "podman network rm -f my-network"; 66 | }; 67 | script = '' 68 | podman network inspect my-network || podman network create my-network 69 | ''; 70 | partOf = [ "podman-compose-myproject-root.target" ]; 71 | wantedBy = [ "podman-compose-myproject-root.target" ]; 72 | }; 73 | systemd.services."podman-network-myproject_test1" = { 74 | path = [ pkgs.podman ]; 75 | serviceConfig = { 76 | Type = "oneshot"; 77 | RemainAfterExit = true; 78 | ExecStop = "podman network rm -f myproject_test1"; 79 | }; 80 | script = '' 81 | podman network inspect myproject_test1 || podman network create myproject_test1 --internal 82 | ''; 83 | partOf = [ "podman-compose-myproject-root.target" ]; 84 | wantedBy = [ "podman-compose-myproject-root.target" ]; 85 | }; 86 | 87 | # Volumes 88 | systemd.services."podman-volume-my-volume" = { 89 | path = [ pkgs.podman ]; 90 | serviceConfig = { 91 | Type = "oneshot"; 92 | RemainAfterExit = true; 93 | }; 94 | script = '' 95 | podman volume inspect my-volume || podman volume create my-volume 96 | ''; 97 | partOf = [ "podman-compose-myproject-root.target" ]; 98 | wantedBy = [ "podman-compose-myproject-root.target" ]; 99 | }; 100 | systemd.services."podman-volume-myproject_test3" = { 101 | path = [ pkgs.podman ]; 102 | serviceConfig = { 103 | Type = "oneshot"; 104 | RemainAfterExit = true; 105 | }; 106 | script = '' 107 | podman volume inspect myproject_test3 || podman volume create myproject_test3 108 | ''; 109 | partOf = [ "podman-compose-myproject-root.target" ]; 110 | wantedBy = [ "podman-compose-myproject-root.target" ]; 111 | }; 112 | 113 | # Root service 114 | # When started, this will automatically create all resources and start 115 | # the containers. When stopped, this will teardown all resources. 116 | systemd.targets."podman-compose-myproject-root" = { 117 | unitConfig = { 118 | Description = "Root target generated by compose2nix."; 119 | }; 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /testdata/TestNetworkSettings.compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | nginx_net: 3 | driver: bridge 4 | enable_ipv6: true 5 | ipam: 6 | config: 7 | - subnet: "2001:1111:3000::/64" 8 | other: 9 | ipam: 10 | driver: dhcp 11 | default: 12 | ipam: 13 | driver: default 14 | -------------------------------------------------------------------------------- /testdata/TestNetworkSettings.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Networks 12 | systemd.services."docker-network-test_default" = { 13 | path = [ pkgs.docker ]; 14 | serviceConfig = { 15 | Type = "oneshot"; 16 | RemainAfterExit = true; 17 | ExecStop = "docker network rm -f test_default"; 18 | }; 19 | script = '' 20 | docker network inspect test_default || docker network create test_default 21 | ''; 22 | partOf = [ "docker-compose-test-root.target" ]; 23 | wantedBy = [ "docker-compose-test-root.target" ]; 24 | }; 25 | systemd.services."docker-network-test_nginx_net" = { 26 | path = [ pkgs.docker ]; 27 | serviceConfig = { 28 | Type = "oneshot"; 29 | RemainAfterExit = true; 30 | ExecStop = "docker network rm -f test_nginx_net"; 31 | }; 32 | script = '' 33 | docker network inspect test_nginx_net || docker network create test_nginx_net --driver=bridge --subnet=2001:1111:3000::/64 --ipv6 34 | ''; 35 | partOf = [ "docker-compose-test-root.target" ]; 36 | wantedBy = [ "docker-compose-test-root.target" ]; 37 | }; 38 | systemd.services."docker-network-test_other" = { 39 | path = [ pkgs.docker ]; 40 | serviceConfig = { 41 | Type = "oneshot"; 42 | RemainAfterExit = true; 43 | ExecStop = "docker network rm -f test_other"; 44 | }; 45 | script = '' 46 | docker network inspect test_other || docker network create test_other --ipam-driver=dhcp 47 | ''; 48 | partOf = [ "docker-compose-test-root.target" ]; 49 | wantedBy = [ "docker-compose-test-root.target" ]; 50 | }; 51 | 52 | # Root service 53 | # When started, this will automatically create all resources and start 54 | # the containers. When stopped, this will teardown all resources. 55 | systemd.targets."docker-compose-test-root" = { 56 | unitConfig = { 57 | Description = "Root target generated by compose2nix."; 58 | }; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /testdata/TestNetworkSettings.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Networks 21 | systemd.services."podman-network-test_default" = { 22 | path = [ pkgs.podman ]; 23 | serviceConfig = { 24 | Type = "oneshot"; 25 | RemainAfterExit = true; 26 | ExecStop = "podman network rm -f test_default"; 27 | }; 28 | script = '' 29 | podman network inspect test_default || podman network create test_default 30 | ''; 31 | partOf = [ "podman-compose-test-root.target" ]; 32 | wantedBy = [ "podman-compose-test-root.target" ]; 33 | }; 34 | systemd.services."podman-network-test_nginx_net" = { 35 | path = [ pkgs.podman ]; 36 | serviceConfig = { 37 | Type = "oneshot"; 38 | RemainAfterExit = true; 39 | ExecStop = "podman network rm -f test_nginx_net"; 40 | }; 41 | script = '' 42 | podman network inspect test_nginx_net || podman network create test_nginx_net --driver=bridge --subnet=2001:1111:3000::/64 --ipv6 43 | ''; 44 | partOf = [ "podman-compose-test-root.target" ]; 45 | wantedBy = [ "podman-compose-test-root.target" ]; 46 | }; 47 | systemd.services."podman-network-test_other" = { 48 | path = [ pkgs.podman ]; 49 | serviceConfig = { 50 | Type = "oneshot"; 51 | RemainAfterExit = true; 52 | ExecStop = "podman network rm -f test_other"; 53 | }; 54 | script = '' 55 | podman network inspect test_other || podman network create test_other --ipam-driver=dhcp 56 | ''; 57 | partOf = [ "podman-compose-test-root.target" ]; 58 | wantedBy = [ "podman-compose-test-root.target" ]; 59 | }; 60 | 61 | # Root service 62 | # When started, this will automatically create all resources and start 63 | # the containers. When stopped, this will teardown all resources. 64 | systemd.targets."podman-compose-test-root" = { 65 | unitConfig = { 66 | Description = "Root target generated by compose2nix."; 67 | }; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /testdata/TestNoCreateRootTarget.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | service: 3 | image: nginx:latest 4 | volumes: 5 | - my-volume:/mnt/volume 6 | networks: 7 | - my-network 8 | 9 | networks: 10 | my-network: 11 | driver: bridge 12 | 13 | volumes: 14 | my-volume: 15 | -------------------------------------------------------------------------------- /testdata/TestNoCreateRootTarget.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-service" = { 13 | image = "nginx:latest"; 14 | volumes = [ 15 | "test_my-volume:/mnt/volume:rw" 16 | ]; 17 | log-driver = "journald"; 18 | autoStart = false; 19 | extraOptions = [ 20 | "--network-alias=service" 21 | "--network=test_my-network" 22 | ]; 23 | }; 24 | systemd.services."docker-test-service" = { 25 | serviceConfig = { 26 | Restart = lib.mkOverride 90 "no"; 27 | }; 28 | after = [ 29 | "docker-network-test_my-network.service" 30 | "docker-volume-test_my-volume.service" 31 | ]; 32 | requires = [ 33 | "docker-network-test_my-network.service" 34 | "docker-volume-test_my-volume.service" 35 | ]; 36 | }; 37 | 38 | # Networks 39 | systemd.services."docker-network-test_my-network" = { 40 | path = [ pkgs.docker ]; 41 | serviceConfig = { 42 | Type = "oneshot"; 43 | RemainAfterExit = true; 44 | ExecStop = "docker network rm -f test_my-network"; 45 | }; 46 | script = '' 47 | docker network inspect test_my-network || docker network create test_my-network --driver=bridge 48 | ''; 49 | }; 50 | 51 | # Volumes 52 | systemd.services."docker-volume-test_my-volume" = { 53 | path = [ pkgs.docker ]; 54 | serviceConfig = { 55 | Type = "oneshot"; 56 | RemainAfterExit = true; 57 | }; 58 | script = '' 59 | docker volume inspect test_my-volume || docker volume create test_my-volume 60 | ''; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /testdata/TestNoCreateRootTarget.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-service" = { 22 | image = "nginx:latest"; 23 | volumes = [ 24 | "test_my-volume:/mnt/volume:rw" 25 | ]; 26 | log-driver = "journald"; 27 | autoStart = false; 28 | extraOptions = [ 29 | "--network-alias=service" 30 | "--network=test_my-network" 31 | ]; 32 | }; 33 | systemd.services."podman-test-service" = { 34 | serviceConfig = { 35 | Restart = lib.mkOverride 90 "no"; 36 | }; 37 | after = [ 38 | "podman-network-test_my-network.service" 39 | "podman-volume-test_my-volume.service" 40 | ]; 41 | requires = [ 42 | "podman-network-test_my-network.service" 43 | "podman-volume-test_my-volume.service" 44 | ]; 45 | }; 46 | 47 | # Networks 48 | systemd.services."podman-network-test_my-network" = { 49 | path = [ pkgs.podman ]; 50 | serviceConfig = { 51 | Type = "oneshot"; 52 | RemainAfterExit = true; 53 | ExecStop = "podman network rm -f test_my-network"; 54 | }; 55 | script = '' 56 | podman network inspect test_my-network || podman network create test_my-network --driver=bridge 57 | ''; 58 | }; 59 | 60 | # Volumes 61 | systemd.services."podman-volume-test_my-volume" = { 62 | path = [ pkgs.podman ]; 63 | serviceConfig = { 64 | Type = "oneshot"; 65 | RemainAfterExit = true; 66 | }; 67 | script = '' 68 | podman volume inspect test_my-volume || podman volume create test_my-volume 69 | ''; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /testdata/TestNoRestart.compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | test: 3 | image: nginx:latest 4 | 5 | -------------------------------------------------------------------------------- /testdata/TestNoRestart.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."test-test" = { 13 | image = "nginx:latest"; 14 | log-driver = "journald"; 15 | autoStart = false; 16 | extraOptions = [ 17 | "--network-alias=test" 18 | "--network=test_default" 19 | ]; 20 | }; 21 | systemd.services."docker-test-test" = { 22 | serviceConfig = { 23 | Restart = lib.mkOverride 90 "no"; 24 | }; 25 | after = [ 26 | "docker-network-test_default.service" 27 | ]; 28 | requires = [ 29 | "docker-network-test_default.service" 30 | ]; 31 | }; 32 | 33 | # Networks 34 | systemd.services."docker-network-test_default" = { 35 | path = [ pkgs.docker ]; 36 | serviceConfig = { 37 | Type = "oneshot"; 38 | RemainAfterExit = true; 39 | ExecStop = "docker network rm -f test_default"; 40 | }; 41 | script = '' 42 | docker network inspect test_default || docker network create test_default 43 | ''; 44 | partOf = [ "docker-compose-test-root.target" ]; 45 | wantedBy = [ "docker-compose-test-root.target" ]; 46 | }; 47 | 48 | # Root service 49 | # When started, this will automatically create all resources and start 50 | # the containers. When stopped, this will teardown all resources. 51 | systemd.targets."docker-compose-test-root" = { 52 | unitConfig = { 53 | Description = "Root target generated by compose2nix."; 54 | }; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /testdata/TestNoRestart.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."test-test" = { 22 | image = "nginx:latest"; 23 | log-driver = "journald"; 24 | autoStart = false; 25 | extraOptions = [ 26 | "--network-alias=test" 27 | "--network=test_default" 28 | ]; 29 | }; 30 | systemd.services."podman-test-test" = { 31 | serviceConfig = { 32 | Restart = lib.mkOverride 90 "no"; 33 | }; 34 | after = [ 35 | "podman-network-test_default.service" 36 | ]; 37 | requires = [ 38 | "podman-network-test_default.service" 39 | ]; 40 | }; 41 | 42 | # Networks 43 | systemd.services."podman-network-test_default" = { 44 | path = [ pkgs.podman ]; 45 | serviceConfig = { 46 | Type = "oneshot"; 47 | RemainAfterExit = true; 48 | ExecStop = "podman network rm -f test_default"; 49 | }; 50 | script = '' 51 | podman network inspect test_default || podman network create test_default 52 | ''; 53 | partOf = [ "podman-compose-test-root.target" ]; 54 | wantedBy = [ "podman-compose-test-root.target" ]; 55 | }; 56 | 57 | # Root service 58 | # When started, this will automatically create all resources and start 59 | # the containers. When stopped, this will teardown all resources. 60 | systemd.targets."podman-compose-test-root" = { 61 | unitConfig = { 62 | Description = "Root target generated by compose2nix."; 63 | }; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /testdata/TestRelativeServiceVolumes.compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | traefik: 4 | container_name: traefik 5 | image: docker.io/library/traefik 6 | volumes: 7 | - ./abc:/abc 8 | - /some/abc:/some/abc 9 | - ../def/xyz:/xyz 10 | - ../def/../abc:/other 11 | - test1:/test1 12 | - test2:/test2 13 | - test3:/test3 14 | restart: unless-stopped 15 | 16 | volumes: 17 | test1: 18 | external: true 19 | test2: 20 | name: my-volume 21 | test3: 22 | 23 | -------------------------------------------------------------------------------- /testdata/TestRelativeServiceVolumes.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."traefik" = { 13 | image = "docker.io/library/traefik"; 14 | volumes = [ 15 | "/my/abc:/other:rw" 16 | "/my/def/xyz:/xyz:rw" 17 | "/my/root/abc:/abc:rw" 18 | "/some/abc:/some/abc:rw" 19 | "my-volume:/test2:rw" 20 | "myproject_test3:/test3:rw" 21 | "test1:/test1:rw" 22 | ]; 23 | log-driver = "journald"; 24 | autoStart = false; 25 | extraOptions = [ 26 | "--network-alias=traefik" 27 | "--network=myproject_default" 28 | ]; 29 | }; 30 | systemd.services."docker-traefik" = { 31 | serviceConfig = { 32 | Restart = lib.mkOverride 90 "always"; 33 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 34 | RestartSec = lib.mkOverride 90 "100ms"; 35 | RestartSteps = lib.mkOverride 90 9; 36 | }; 37 | after = [ 38 | "docker-network-myproject_default.service" 39 | "docker-volume-my-volume.service" 40 | "docker-volume-myproject_test3.service" 41 | ]; 42 | requires = [ 43 | "docker-network-myproject_default.service" 44 | "docker-volume-my-volume.service" 45 | "docker-volume-myproject_test3.service" 46 | ]; 47 | }; 48 | 49 | # Networks 50 | systemd.services."docker-network-myproject_default" = { 51 | path = [ pkgs.docker ]; 52 | serviceConfig = { 53 | Type = "oneshot"; 54 | RemainAfterExit = true; 55 | ExecStop = "docker network rm -f myproject_default"; 56 | }; 57 | script = '' 58 | docker network inspect myproject_default || docker network create myproject_default 59 | ''; 60 | partOf = [ "docker-compose-myproject-root.target" ]; 61 | wantedBy = [ "docker-compose-myproject-root.target" ]; 62 | }; 63 | 64 | # Volumes 65 | systemd.services."docker-volume-my-volume" = { 66 | path = [ pkgs.docker ]; 67 | serviceConfig = { 68 | Type = "oneshot"; 69 | RemainAfterExit = true; 70 | }; 71 | script = '' 72 | docker volume inspect my-volume || docker volume create my-volume 73 | ''; 74 | partOf = [ "docker-compose-myproject-root.target" ]; 75 | wantedBy = [ "docker-compose-myproject-root.target" ]; 76 | }; 77 | systemd.services."docker-volume-myproject_test3" = { 78 | path = [ pkgs.docker ]; 79 | serviceConfig = { 80 | Type = "oneshot"; 81 | RemainAfterExit = true; 82 | }; 83 | script = '' 84 | docker volume inspect myproject_test3 || docker volume create myproject_test3 85 | ''; 86 | partOf = [ "docker-compose-myproject-root.target" ]; 87 | wantedBy = [ "docker-compose-myproject-root.target" ]; 88 | }; 89 | 90 | # Root service 91 | # When started, this will automatically create all resources and start 92 | # the containers. When stopped, this will teardown all resources. 93 | systemd.targets."docker-compose-myproject-root" = { 94 | unitConfig = { 95 | Description = "Root target generated by compose2nix."; 96 | }; 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /testdata/TestRelativeServiceVolumes.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."traefik" = { 22 | image = "docker.io/library/traefik"; 23 | volumes = [ 24 | "/my/abc:/other:rw" 25 | "/my/def/xyz:/xyz:rw" 26 | "/my/root/abc:/abc:rw" 27 | "/some/abc:/some/abc:rw" 28 | "my-volume:/test2:rw" 29 | "myproject_test3:/test3:rw" 30 | "test1:/test1:rw" 31 | ]; 32 | log-driver = "journald"; 33 | autoStart = false; 34 | extraOptions = [ 35 | "--network-alias=traefik" 36 | "--network=myproject_default" 37 | ]; 38 | }; 39 | systemd.services."podman-traefik" = { 40 | serviceConfig = { 41 | Restart = lib.mkOverride 90 "always"; 42 | }; 43 | after = [ 44 | "podman-network-myproject_default.service" 45 | "podman-volume-my-volume.service" 46 | "podman-volume-myproject_test3.service" 47 | ]; 48 | requires = [ 49 | "podman-network-myproject_default.service" 50 | "podman-volume-my-volume.service" 51 | "podman-volume-myproject_test3.service" 52 | ]; 53 | }; 54 | 55 | # Networks 56 | systemd.services."podman-network-myproject_default" = { 57 | path = [ pkgs.podman ]; 58 | serviceConfig = { 59 | Type = "oneshot"; 60 | RemainAfterExit = true; 61 | ExecStop = "podman network rm -f myproject_default"; 62 | }; 63 | script = '' 64 | podman network inspect myproject_default || podman network create myproject_default 65 | ''; 66 | partOf = [ "podman-compose-myproject-root.target" ]; 67 | wantedBy = [ "podman-compose-myproject-root.target" ]; 68 | }; 69 | 70 | # Volumes 71 | systemd.services."podman-volume-my-volume" = { 72 | path = [ pkgs.podman ]; 73 | serviceConfig = { 74 | Type = "oneshot"; 75 | RemainAfterExit = true; 76 | }; 77 | script = '' 78 | podman volume inspect my-volume || podman volume create my-volume 79 | ''; 80 | partOf = [ "podman-compose-myproject-root.target" ]; 81 | wantedBy = [ "podman-compose-myproject-root.target" ]; 82 | }; 83 | systemd.services."podman-volume-myproject_test3" = { 84 | path = [ pkgs.podman ]; 85 | serviceConfig = { 86 | Type = "oneshot"; 87 | RemainAfterExit = true; 88 | }; 89 | script = '' 90 | podman volume inspect myproject_test3 || podman volume create myproject_test3 91 | ''; 92 | partOf = [ "podman-compose-myproject-root.target" ]; 93 | wantedBy = [ "podman-compose-myproject-root.target" ]; 94 | }; 95 | 96 | # Root service 97 | # When started, this will automatically create all resources and start 98 | # the containers. When stopped, this will teardown all resources. 99 | systemd.targets."podman-compose-myproject-root" = { 100 | unitConfig = { 101 | Description = "Root target generated by compose2nix."; 102 | }; 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /testdata/TestRelativeServiceVolumes_CurrentDirectory.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Containers 12 | virtualisation.oci-containers.containers."traefik" = { 13 | image = "docker.io/library/traefik"; 14 | volumes = [ 15 | "/some/abc:/other:rw" 16 | "/some/def/xyz:/xyz:rw" 17 | "/some/path/abc:/abc:rw" 18 | "my-volume:/test2:rw" 19 | "myproject_test3:/test3:rw" 20 | "test1:/test1:rw" 21 | ]; 22 | log-driver = "journald"; 23 | autoStart = false; 24 | extraOptions = [ 25 | "--network-alias=traefik" 26 | "--network=myproject_default" 27 | ]; 28 | }; 29 | systemd.services."docker-traefik" = { 30 | serviceConfig = { 31 | Restart = lib.mkOverride 90 "always"; 32 | RestartMaxDelaySec = lib.mkOverride 90 "1m"; 33 | RestartSec = lib.mkOverride 90 "100ms"; 34 | RestartSteps = lib.mkOverride 90 9; 35 | }; 36 | after = [ 37 | "docker-network-myproject_default.service" 38 | "docker-volume-my-volume.service" 39 | "docker-volume-myproject_test3.service" 40 | ]; 41 | requires = [ 42 | "docker-network-myproject_default.service" 43 | "docker-volume-my-volume.service" 44 | "docker-volume-myproject_test3.service" 45 | ]; 46 | }; 47 | 48 | # Networks 49 | systemd.services."docker-network-myproject_default" = { 50 | path = [ pkgs.docker ]; 51 | serviceConfig = { 52 | Type = "oneshot"; 53 | RemainAfterExit = true; 54 | ExecStop = "docker network rm -f myproject_default"; 55 | }; 56 | script = '' 57 | docker network inspect myproject_default || docker network create myproject_default 58 | ''; 59 | partOf = [ "docker-compose-myproject-root.target" ]; 60 | wantedBy = [ "docker-compose-myproject-root.target" ]; 61 | }; 62 | 63 | # Volumes 64 | systemd.services."docker-volume-my-volume" = { 65 | path = [ pkgs.docker ]; 66 | serviceConfig = { 67 | Type = "oneshot"; 68 | RemainAfterExit = true; 69 | }; 70 | script = '' 71 | docker volume inspect my-volume || docker volume create my-volume 72 | ''; 73 | partOf = [ "docker-compose-myproject-root.target" ]; 74 | wantedBy = [ "docker-compose-myproject-root.target" ]; 75 | }; 76 | systemd.services."docker-volume-myproject_test3" = { 77 | path = [ pkgs.docker ]; 78 | serviceConfig = { 79 | Type = "oneshot"; 80 | RemainAfterExit = true; 81 | }; 82 | script = '' 83 | docker volume inspect myproject_test3 || docker volume create myproject_test3 84 | ''; 85 | partOf = [ "docker-compose-myproject-root.target" ]; 86 | wantedBy = [ "docker-compose-myproject-root.target" ]; 87 | }; 88 | 89 | # Root service 90 | # When started, this will automatically create all resources and start 91 | # the containers. When stopped, this will teardown all resources. 92 | systemd.targets."docker-compose-myproject-root" = { 93 | unitConfig = { 94 | Description = "Root target generated by compose2nix."; 95 | }; 96 | }; 97 | } 98 | -------------------------------------------------------------------------------- /testdata/TestRelativeServiceVolumes_CurrentDirectory.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Containers 21 | virtualisation.oci-containers.containers."traefik" = { 22 | image = "docker.io/library/traefik"; 23 | volumes = [ 24 | "/some/abc:/other:rw" 25 | "/some/def/xyz:/xyz:rw" 26 | "/some/path/abc:/abc:rw" 27 | "my-volume:/test2:rw" 28 | "myproject_test3:/test3:rw" 29 | "test1:/test1:rw" 30 | ]; 31 | log-driver = "journald"; 32 | autoStart = false; 33 | extraOptions = [ 34 | "--network-alias=traefik" 35 | "--network=myproject_default" 36 | ]; 37 | }; 38 | systemd.services."podman-traefik" = { 39 | serviceConfig = { 40 | Restart = lib.mkOverride 90 "always"; 41 | }; 42 | after = [ 43 | "podman-network-myproject_default.service" 44 | "podman-volume-my-volume.service" 45 | "podman-volume-myproject_test3.service" 46 | ]; 47 | requires = [ 48 | "podman-network-myproject_default.service" 49 | "podman-volume-my-volume.service" 50 | "podman-volume-myproject_test3.service" 51 | ]; 52 | }; 53 | 54 | # Networks 55 | systemd.services."podman-network-myproject_default" = { 56 | path = [ pkgs.podman ]; 57 | serviceConfig = { 58 | Type = "oneshot"; 59 | RemainAfterExit = true; 60 | ExecStop = "podman network rm -f myproject_default"; 61 | }; 62 | script = '' 63 | podman network inspect myproject_default || podman network create myproject_default 64 | ''; 65 | partOf = [ "podman-compose-myproject-root.target" ]; 66 | wantedBy = [ "podman-compose-myproject-root.target" ]; 67 | }; 68 | 69 | # Volumes 70 | systemd.services."podman-volume-my-volume" = { 71 | path = [ pkgs.podman ]; 72 | serviceConfig = { 73 | Type = "oneshot"; 74 | RemainAfterExit = true; 75 | }; 76 | script = '' 77 | podman volume inspect my-volume || podman volume create my-volume 78 | ''; 79 | partOf = [ "podman-compose-myproject-root.target" ]; 80 | wantedBy = [ "podman-compose-myproject-root.target" ]; 81 | }; 82 | systemd.services."podman-volume-myproject_test3" = { 83 | path = [ pkgs.podman ]; 84 | serviceConfig = { 85 | Type = "oneshot"; 86 | RemainAfterExit = true; 87 | }; 88 | script = '' 89 | podman volume inspect myproject_test3 || podman volume create myproject_test3 90 | ''; 91 | partOf = [ "podman-compose-myproject-root.target" ]; 92 | wantedBy = [ "podman-compose-myproject-root.target" ]; 93 | }; 94 | 95 | # Root service 96 | # When started, this will automatically create all resources and start 97 | # the containers. When stopped, this will teardown all resources. 98 | systemd.targets."podman-compose-myproject-root" = { 99 | unitConfig = { 100 | Description = "Root target generated by compose2nix."; 101 | }; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /testdata/TestUnusedResources.compose.yml: -------------------------------------------------------------------------------- 1 | networks: 2 | test: 3 | volumes: 4 | some-volume: 5 | 6 | -------------------------------------------------------------------------------- /testdata/TestUnusedResources.docker.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.docker = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | }; 9 | virtualisation.oci-containers.backend = "docker"; 10 | 11 | # Root service 12 | # When started, this will automatically create all resources and start 13 | # the containers. When stopped, this will teardown all resources. 14 | systemd.targets."docker-compose-myproject-root" = { 15 | unitConfig = { 16 | Description = "Root target generated by compose2nix."; 17 | }; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /testdata/TestUnusedResources.podman.nix: -------------------------------------------------------------------------------- 1 | { pkgs, lib, config, ... }: 2 | 3 | { 4 | # Runtime 5 | virtualisation.podman = { 6 | enable = true; 7 | autoPrune.enable = true; 8 | dockerCompat = true; 9 | }; 10 | 11 | # Enable container name DNS for all Podman networks. 12 | networking.firewall.interfaces = let 13 | matchAll = if !config.networking.nftables.enable then "podman+" else "podman*"; 14 | in { 15 | "${matchAll}".allowedUDPPorts = [ 53 ]; 16 | }; 17 | 18 | virtualisation.oci-containers.backend = "podman"; 19 | 20 | # Root service 21 | # When started, this will automatically create all resources and start 22 | # the containers. When stopped, this will teardown all resources. 23 | systemd.targets."podman-compose-myproject-root" = { 24 | unitConfig = { 25 | Description = "Root target generated by compose2nix."; 26 | }; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /testdata/compose.yml: -------------------------------------------------------------------------------- 1 | name: "myproject" 2 | services: 3 | sabnzbd: 4 | image: lscr.io/linuxserver/sabnzbd 5 | hostname: sabnzbd 6 | environment: 7 | PUID: ${PUID} 8 | PGID: ${PGID} 9 | TZ: ${TIMEZONE} 10 | DOCKER_MODS: ghcr.io/gilbn/theme.park:sabnzbd 11 | TP_THEME: ${THEMEPARK_THEME} 12 | TP_DOMAIN: ${HOME_DOMAIN}\/themepark 13 | TP_HOTIO: "false" 14 | volumes: 15 | - /var/volumes/sabnzbd:/config 16 | - storage:/storage 17 | labels: 18 | - "traefik.enable=true" 19 | - "traefik.http.routers.sabnzbd.rule=Host(`${HOME_DOMAIN}`) && PathPrefix(`/sabnzbd`)" 20 | - "traefik.http.routers.sabnzbd.tls.certresolver=htpc" 21 | - "traefik.http.routers.sabnzbd.middlewares=chain-authelia@file" 22 | - "compose2nix.systemd.service.RuntimeMaxSec=10" 23 | - "compose2nix.systemd.unit.Description=This is the sabnzbd container!" 24 | logging: 25 | driver: "json-file" 26 | options: 27 | max-size: 10m 28 | max-file: "3" 29 | compress: "true" 30 | healthcheck: 31 | test: "curl -f http://localhost/" 32 | restart: unless-stopped 33 | transmission: 34 | image: docker.io/haugene/transmission-openvpn 35 | container_name: torrent-client 36 | privileged: true 37 | cap_add: 38 | - NET_ADMIN 39 | devices: 40 | - /dev/net/tun:/dev/net/tun 41 | dns: 42 | - 8.8.8.8 43 | - 8.8.4.4 44 | sysctls: 45 | net.ipv6.conf.all.disable_ipv6: 0 46 | ports: 47 | - "9091:9091" 48 | volumes: 49 | - /etc/localtime:/etc/localtime:ro 50 | - /var/volumes/transmission/config:/config 51 | - /var/volumes/transmission/scripts:/scripts 52 | - storage:/storage 53 | networks: 54 | something: 55 | aliases: 56 | - "my-torrent-client" 57 | extra_hosts: 58 | - "abc:93.184.216.34" 59 | - "abc:::1" 60 | depends_on: 61 | - sabnzbd 62 | shm_size: 64m 63 | environment: 64 | TZ: ${TIMEZONE} 65 | PUID: ${PUID} 66 | PGID: ${PGID} 67 | 68 | # Do not try to chown the download directories. 69 | GLOBAL_APPLY_PERMISSIONS: "false" 70 | 71 | TRANSMISSION_HOME: /config/transmission-home 72 | 73 | LOCAL_NETWORK: 192.168.0.0/16 74 | 75 | # Disable DHT and PEX for private trackers. 76 | TRANSMISSION_DHT_ENABLED: "false" 77 | TRANSMISSION_PEX_ENABLED: "false" 78 | 79 | # Directories 80 | TRANSMISSION_DOWNLOAD_DIR: /storage/Downloads/transmission 81 | TRANSMISSION_INCOMPLETE_DIR: /storage/Downloads/transmission/incomplete 82 | TRANSMISSION_INCOMPLETE_DIR_ENABLED: "true" 83 | 84 | # Script to automatically unrar downloads in Transmission. 85 | # Make sure to set perms to 655. 86 | TRANSMISSION_SCRIPT_TORRENT_DONE_ENABLED: "true" 87 | TRANSMISSION_SCRIPT_TORRENT_DONE_FILENAME: /config/transmission-unpack.sh 88 | labels: 89 | - "traefik.enable=true" 90 | - "traefik.http.services.transmission.loadbalancer.server.port=9091" 91 | - "traefik.http.routers.transmission.rule=Host(`${HOME_DOMAIN}`) && PathPrefix(`/transmission`)" 92 | - "traefik.http.routers.transmission.tls.certresolver=htpc" 93 | - "traefik.http.routers.transmission.middlewares=chain-authelia@file" 94 | - "autoheal=true" 95 | - "compose2nix.settings.autoStart=false" 96 | healthcheck: 97 | disable: true 98 | # Restart 3 times on failure. This can happen when the VPN sub expires. 99 | restart: on-failure:3 100 | jellyseerr: 101 | image: docker.io/fallenbagel/jellyseerr:latest 102 | container_name: jellyseerr 103 | environment: 104 | PUID: ${PUID} 105 | PGID: ${PGID} 106 | TZ: ${TIMEZONE} 107 | dns: 108 | - 1.1.1.1 109 | volumes: 110 | - /var/volumes/jellyseerr:/app/config 111 | - books:/books 112 | labels: 113 | - "traefik.enable=true" 114 | - "traefik.http.routers.jellyseerr.rule=Host(`requests.${DOMAIN}`)" 115 | - "traefik.http.routers.jellyseerr.tls.certresolver=htpc" 116 | - "traefik.http.routers.jellyseerr.middlewares=chain-authelia@file" 117 | network_mode: "service:sabnzbd" 118 | deploy: 119 | restart_policy: 120 | condition: on-failure 121 | delay: 5s 122 | max_attempts: 3 123 | window: 120s 124 | resources: 125 | limits: 126 | cpus: 1.5 127 | memory: 1000M 128 | reservations: 129 | cpus: 1.0 # ignored 130 | memory: 500M 131 | logging: 132 | driver: "json-file" 133 | options: 134 | max-size: 10m 135 | max-file: "3" 136 | compress: "true" 137 | command: ls -la / 138 | healthcheck: 139 | # Double-$ is used to escape Compose env variable evaluation. 140 | test: ["CMD-SHELL", "curl -f http://localhost/$${POTATO}"] 141 | restart: unless-stopped 142 | photoprism-mariadb: 143 | image: docker.io/library/mariadb:10.9 144 | container_name: photoprism-mariadb 145 | environment: 146 | MARIADB_AUTO_UPGRADE: "1" 147 | MARIADB_INITDB_SKIP_TZINFO: "1" 148 | MARIADB_DATABASE: "photoprism" 149 | MARIADB_USER: "photoprism" 150 | MARIADB_PASSWORD: "insecure" 151 | MARIADB_ROOT_PASSWORD: "insecure" 152 | network_mode: "host" 153 | user: "${PUID}:${PGID}" 154 | volumes: 155 | - /var/volumes/photoprism-mariadb:/var/lib/mysql 156 | - photos:/photos 157 | deploy: 158 | restart_policy: 159 | condition: any 160 | delay: 3m 161 | max_attempts: 10 162 | logging: 163 | driver: "json-file" 164 | options: 165 | max-size: 10m 166 | max-file: "3" 167 | compress: "true" 168 | healthcheck: 169 | test: ["CMD", "curl", "-f", "http://localhost"] 170 | interval: 1m30s 171 | timeout: 10s 172 | retries: 3 173 | start_period: 40s 174 | start_interval: 5s 175 | restart: unless-stopped 176 | traefik: 177 | container_name: traefik 178 | image: docker.io/library/traefik 179 | ports: 180 | - "80:80" 181 | - "443:443" 182 | environment: 183 | CLOUDFLARE_EMAIL: ${CLOUDFLARE_EMAIL} 184 | CLOUDFLARE_API_KEY: ${CLOUDFLARE_API_KEY} 185 | volumes: 186 | - /var/run/podman/podman.sock:/var/run/docker.sock:ro 187 | - /var/volumes/traefik:/etc/traefik 188 | labels: 189 | - "traefik.enable=true" 190 | - "traefik.http.routers.traefik.rule=Host(`${HOME_DOMAIN}`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" 191 | - "traefik.http.routers.traefik.entrypoints=https" 192 | - "traefik.http.routers.traefik.service=api@internal" 193 | - "traefik.http.routers.traefik.tls.certresolver=htpc" 194 | - "traefik.http.routers.traefik.middlewares=chain-authelia@file" 195 | - "compose2nix.systemd.service.Restart='no'" 196 | - "compose2nix.systemd.unit.AllowIsolate=true" 197 | network_mode: "container:sabnzbd" 198 | logging: 199 | driver: "json-file" 200 | options: 201 | max-size: 10m 202 | max-file: "3" 203 | compress: "true" 204 | 205 | networks: 206 | something: 207 | labels: 208 | - "test-label=okay" 209 | volumes: 210 | storage: 211 | name: storage 212 | driver_opts: 213 | type: none 214 | device: /mnt/media 215 | o: bind 216 | books: 217 | driver_opts: 218 | type: none 219 | device: /mnt/media/Books 220 | o: bind 221 | photos: 222 | name: photos 223 | labels: 224 | - "test-label=okay" 225 | driver_opts: 226 | type: none 227 | device: /mnt/photos 228 | o: bind 229 | -------------------------------------------------------------------------------- /testdata/first.env: -------------------------------------------------------------------------------- 1 | NAME="first" 2 | DEPTH=10 3 | -------------------------------------------------------------------------------- /testdata/input.env: -------------------------------------------------------------------------------- 1 | DOMAIN=hello.us 2 | HOME_DOMAIN=hey.hello.us 3 | PUID=1000 4 | PGID=1000 5 | TIMEZONE=America/New_York 6 | THEMEPARK_THEME=potato 7 | CLOUDFLARE_EMAIL=aaa@aaa.com 8 | # a comment 9 | CLOUDFLARE_API_KEY=yomama 10 | -------------------------------------------------------------------------------- /testdata/second.env: -------------------------------------------------------------------------------- 1 | NAME="second" 2 | DEPTH=20 3 | --------------------------------------------------------------------------------