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