1{ config, lib, pkgs, ... }:
2let
3 cfg = config.virtualisation.podman;
4 toml = pkgs.formats.toml { };
5 json = pkgs.formats.json { };
6
7 inherit (lib) mkOption types;
8
9 podmanPackage = (pkgs.podman.override {
10 extraPackages = cfg.extraPackages
11 ++ lib.optional (builtins.elem "zfs" config.boot.supportedFilesystems) config.boot.zfs.package;
12 });
13
14 # Provides a fake "docker" binary mapping to podman
15 dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}"
16 {
17 outputs = [ "out" "man" ];
18 inherit (podmanPackage) meta;
19 } ''
20 mkdir -p $out/bin
21 ln -s ${podmanPackage}/bin/podman $out/bin/docker
22
23 mkdir -p $man/share/man/man1
24 for f in ${podmanPackage.man}/share/man/man1/*; do
25 basename=$(basename $f | sed s/podman/docker/g)
26 ln -s $f $man/share/man/man1/$basename
27 done
28 '';
29
30 net-conflist = pkgs.runCommand "87-podman-bridge.conflist"
31 {
32 nativeBuildInputs = [ pkgs.jq ];
33 extraPlugins = builtins.toJSON cfg.defaultNetwork.extraPlugins;
34 jqScript = ''
35 . + { "plugins": (.plugins + $extraPlugins) }
36 '';
37 } ''
38 jq <${cfg.package}/etc/cni/net.d/87-podman-bridge.conflist \
39 --argjson extraPlugins "$extraPlugins" \
40 "$jqScript" \
41 >$out
42 '';
43
44in
45{
46 imports = [
47 ./dnsname.nix
48 ./network-socket.nix
49 ];
50
51 meta = {
52 maintainers = lib.teams.podman.members;
53 };
54
55 options.virtualisation.podman = {
56
57 enable =
58 mkOption {
59 type = types.bool;
60 default = false;
61 description = lib.mdDoc ''
62 This option enables Podman, a daemonless container engine for
63 developing, managing, and running OCI Containers on your Linux System.
64
65 It is a drop-in replacement for the {command}`docker` command.
66 '';
67 };
68
69 dockerSocket.enable = mkOption {
70 type = types.bool;
71 default = false;
72 description = lib.mdDoc ''
73 Make the Podman socket available in place of the Docker socket, so
74 Docker tools can find the Podman socket.
75
76 Podman implements the Docker API.
77
78 Users must be in the `podman` group in order to connect. As
79 with Docker, members of this group can gain root access.
80 '';
81 };
82
83 dockerCompat = mkOption {
84 type = types.bool;
85 default = false;
86 description = lib.mdDoc ''
87 Create an alias mapping {command}`docker` to {command}`podman`.
88 '';
89 };
90
91 enableNvidia = mkOption {
92 type = types.bool;
93 default = false;
94 description = lib.mdDoc ''
95 Enable use of NVidia GPUs from within podman containers.
96 '';
97 };
98
99 extraPackages = mkOption {
100 type = with types; listOf package;
101 default = [ ];
102 example = lib.literalExpression ''
103 [
104 pkgs.gvisor
105 ]
106 '';
107 description = lib.mdDoc ''
108 Extra packages to be installed in the Podman wrapper.
109 '';
110 };
111
112 package = lib.mkOption {
113 type = types.package;
114 default = podmanPackage;
115 internal = true;
116 description = lib.mdDoc ''
117 The final Podman package (including extra packages).
118 '';
119 };
120
121 defaultNetwork.extraPlugins = lib.mkOption {
122 type = types.listOf json.type;
123 default = [ ];
124 description = lib.mdDoc ''
125 Extra CNI plugin configurations to add to podman's default network.
126 '';
127 };
128
129 };
130
131 config = lib.mkIf cfg.enable (lib.mkMerge [
132 {
133 environment.systemPackages = [ cfg.package ]
134 ++ lib.optional cfg.dockerCompat dockerCompat;
135
136 environment.etc."cni/net.d/87-podman-bridge.conflist".source = net-conflist;
137
138 virtualisation.containers = {
139 enable = true; # Enable common /etc/containers configuration
140 containersConf.settings = lib.optionalAttrs cfg.enableNvidia {
141 engine = {
142 conmon_env_vars = [ "PATH=${lib.makeBinPath [ pkgs.nvidia-podman ]}" ];
143 runtimes.nvidia = [ "${pkgs.nvidia-podman}/bin/nvidia-container-runtime" ];
144 };
145 };
146 };
147
148 systemd.packages = [ cfg.package ];
149
150 systemd.services.podman.serviceConfig = {
151 ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
152 };
153
154 systemd.sockets.podman.wantedBy = [ "sockets.target" ];
155 systemd.sockets.podman.socketConfig.SocketGroup = "podman";
156
157 systemd.user.services.podman.serviceConfig = {
158 ExecStart = [ "" "${cfg.package}/bin/podman $LOGGING system service" ];
159 };
160
161 systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
162
163 systemd.tmpfiles.packages = [
164 # The /run/podman rule interferes with our podman group, so we remove
165 # it and let the systemd socket logic take care of it.
166 (pkgs.runCommand "podman-tmpfiles-nixos" { package = cfg.package; } ''
167 mkdir -p $out/lib/tmpfiles.d/
168 grep -v 'D! /run/podman 0700 root root' \
169 <$package/lib/tmpfiles.d/podman.conf \
170 >$out/lib/tmpfiles.d/podman.conf
171 '')
172 ];
173
174 systemd.tmpfiles.rules =
175 lib.optionals cfg.dockerSocket.enable [
176 "L! /run/docker.sock - - - - /run/podman/podman.sock"
177 ];
178
179 users.groups.podman = { };
180
181 assertions = [
182 {
183 assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
184 message = "Option dockerCompat conflicts with docker";
185 }
186 {
187 assertion = cfg.dockerSocket.enable -> !config.virtualisation.docker.enable;
188 message = ''
189 The options virtualisation.podman.dockerSocket.enable and virtualisation.docker.enable conflict, because only one can serve the socket.
190 '';
191 }
192 ];
193 }
194 ]);
195}