1{ config, lib, pkgs, ... }:
2let
3 cfg = config.virtualisation.podman;
4 json = pkgs.formats.json { };
5
6 inherit (lib) mkOption types;
7
8 podmanPackage = (pkgs.podman.override {
9 extraPackages = cfg.extraPackages
10 # setuid shadow
11 ++ [ "/run/wrappers" ]
12 ++ lib.optional (config.boot.supportedFilesystems.zfs or false) config.boot.zfs.package;
13 });
14
15 # Provides a fake "docker" binary mapping to podman
16 dockerCompat = pkgs.runCommand "${podmanPackage.pname}-docker-compat-${podmanPackage.version}"
17 {
18 outputs = [ "out" "man" ];
19 inherit (podmanPackage) meta;
20 } ''
21 mkdir -p $out/bin
22 ln -s ${podmanPackage}/bin/podman $out/bin/docker
23
24 mkdir -p $man/share/man/man1
25 for f in ${podmanPackage.man}/share/man/man1/*; do
26 basename=$(basename $f | sed s/podman/docker/g)
27 ln -s $f $man/share/man/man1/$basename
28 done
29 '';
30
31in
32{
33 imports = [
34 (lib.mkRemovedOptionModule [ "virtualisation" "podman" "defaultNetwork" "dnsname" ]
35 "Use virtualisation.podman.defaultNetwork.settings.dns_enabled instead.")
36 (lib.mkRemovedOptionModule [ "virtualisation" "podman" "defaultNetwork" "extraPlugins" ]
37 "Netavark isn't compatible with CNI plugins.")
38 ./network-socket.nix
39 ];
40
41 meta = {
42 maintainers = lib.teams.podman.members;
43 };
44
45 options.virtualisation.podman = {
46
47 enable =
48 mkOption {
49 type = types.bool;
50 default = false;
51 description = ''
52 This option enables Podman, a daemonless container engine for
53 developing, managing, and running OCI Containers on your Linux System.
54
55 It is a drop-in replacement for the {command}`docker` command.
56 '';
57 };
58
59 dockerSocket.enable = mkOption {
60 type = types.bool;
61 default = false;
62 description = ''
63 Make the Podman socket available in place of the Docker socket, so
64 Docker tools can find the Podman socket.
65
66 Podman implements the Docker API.
67
68 Users must be in the `podman` group in order to connect. As
69 with Docker, members of this group can gain root access.
70 '';
71 };
72
73 dockerCompat = mkOption {
74 type = types.bool;
75 default = false;
76 description = ''
77 Create an alias mapping {command}`docker` to {command}`podman`.
78 '';
79 };
80
81 enableNvidia = mkOption {
82 type = types.bool;
83 default = false;
84 description = ''
85 **Deprecated**, please use virtualisation.containers.cdi.dynamic.nvidia.enable instead.
86
87 Enable use of NVidia GPUs from within podman containers.
88 '';
89 };
90
91 extraPackages = mkOption {
92 type = with types; listOf package;
93 default = [ ];
94 example = lib.literalExpression ''
95 [
96 pkgs.gvisor
97 ]
98 '';
99 description = ''
100 Extra packages to be installed in the Podman wrapper.
101 '';
102 };
103
104 autoPrune = {
105 enable = mkOption {
106 type = types.bool;
107 default = false;
108 description = ''
109 Whether to periodically prune Podman resources. If enabled, a
110 systemd timer will run `podman system prune -f`
111 as specified by the `dates` option.
112 '';
113 };
114
115 flags = mkOption {
116 type = types.listOf types.str;
117 default = [];
118 example = [ "--all" ];
119 description = ''
120 Any additional flags passed to {command}`podman system prune`.
121 '';
122 };
123
124 dates = mkOption {
125 default = "weekly";
126 type = types.str;
127 description = ''
128 Specification (in the format described by
129 {manpage}`systemd.time(7)`) of the time at
130 which the prune will occur.
131 '';
132 };
133 };
134
135 package = lib.mkOption {
136 type = types.package;
137 default = podmanPackage;
138 internal = true;
139 description = ''
140 The final Podman package (including extra packages).
141 '';
142 };
143
144 defaultNetwork.settings = lib.mkOption {
145 type = json.type;
146 default = { };
147 example = lib.literalExpression "{ dns_enabled = true; }";
148 description = ''
149 Settings for podman's default network.
150 '';
151 };
152
153 };
154
155 config =
156 let
157 networkConfig = ({
158 dns_enabled = false;
159 driver = "bridge";
160 id = "0000000000000000000000000000000000000000000000000000000000000000";
161 internal = false;
162 ipam_options = { driver = "host-local"; };
163 ipv6_enabled = false;
164 name = "podman";
165 network_interface = "podman0";
166 subnets = [{ gateway = "10.88.0.1"; subnet = "10.88.0.0/16"; }];
167 } // cfg.defaultNetwork.settings);
168 inherit (networkConfig) dns_enabled network_interface;
169 in
170 lib.mkIf cfg.enable {
171 warnings = lib.optionals cfg.enableNvidia [
172 ''
173 You have set virtualisation.podman.enableNvidia. This option is deprecated, please set virtualisation.containers.cdi.dynamic.nvidia.enable instead.
174 ''
175 ];
176
177 environment.systemPackages = [ cfg.package ]
178 ++ lib.optional cfg.dockerCompat dockerCompat;
179
180 # https://github.com/containers/podman/blob/097cc6eb6dd8e598c0e8676d21267b4edb11e144/docs/tutorials/basic_networking.md#default-network
181 environment.etc."containers/networks/podman.json" = lib.mkIf (cfg.defaultNetwork.settings != { }) {
182 source = json.generate "podman.json" networkConfig;
183 };
184
185 # containers cannot reach aardvark-dns otherwise
186 networking.firewall.interfaces.${network_interface}.allowedUDPPorts = lib.mkIf dns_enabled [ 53 ];
187
188 virtualisation.containers = {
189 enable = true; # Enable common /etc/containers configuration
190 containersConf.settings = {
191 network.network_backend = "netavark";
192 } // lib.optionalAttrs cfg.enableNvidia {
193 engine = {
194 conmon_env_vars = [ "PATH=${lib.makeBinPath [ pkgs.nvidia-podman ]}" ];
195 runtimes.nvidia = [ "${pkgs.nvidia-podman}/bin/nvidia-container-runtime" ];
196 };
197 };
198 };
199
200 systemd.packages = [ cfg.package ];
201
202 systemd.services.podman-prune = {
203 description = "Prune podman resources";
204
205 restartIfChanged = false;
206 unitConfig.X-StopOnRemoval = false;
207
208 serviceConfig.Type = "oneshot";
209
210 script = ''
211 ${cfg.package}/bin/podman system prune -f ${toString cfg.autoPrune.flags}
212 '';
213
214 startAt = lib.optional cfg.autoPrune.enable cfg.autoPrune.dates;
215 after = [ "podman.service" ];
216 requires = [ "podman.service" ];
217 };
218
219 systemd.services.podman.environment = config.networking.proxy.envVars;
220 systemd.sockets.podman.wantedBy = [ "sockets.target" ];
221 systemd.sockets.podman.socketConfig.SocketGroup = "podman";
222 # Podman does not support multiple sockets, as of podman 5.0.2, so we use
223 # a symlink. Unfortunately this does not let us use an alternate group,
224 # such as `docker`.
225 systemd.sockets.podman.socketConfig.Symlinks =
226 lib.mkIf cfg.dockerSocket.enable [ "/run/docker.sock" ];
227
228 systemd.user.services.podman.environment = config.networking.proxy.envVars;
229 systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
230
231 systemd.timers.podman-prune.timerConfig = lib.mkIf cfg.autoPrune.enable {
232 Persistent = true;
233 RandomizedDelaySec = 1800;
234 };
235
236 systemd.tmpfiles.packages = [
237 # The /run/podman rule interferes with our podman group, so we remove
238 # it and let the systemd socket logic take care of it.
239 (pkgs.runCommand "podman-tmpfiles-nixos" { package = cfg.package; } ''
240 mkdir -p $out/lib/tmpfiles.d/
241 grep -v 'D! /run/podman 0700 root root' \
242 <$package/lib/tmpfiles.d/podman.conf \
243 >$out/lib/tmpfiles.d/podman.conf
244 '')
245 ];
246
247 users.groups.podman = { };
248
249 assertions = [
250 {
251 assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
252 message = "Option dockerCompat conflicts with docker";
253 }
254 {
255 assertion = cfg.dockerSocket.enable -> !config.virtualisation.docker.enable;
256 message = ''
257 The options virtualisation.podman.dockerSocket.enable and virtualisation.docker.enable conflict, because only one can serve the socket.
258 '';
259 }
260 ];
261 };
262}