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 (builtins.elem "zfs" config.boot.supportedFilesystems) 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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
85 Enable use of NVidia GPUs from within podman containers.
86 '';
87 };
88
89 extraPackages = mkOption {
90 type = with types; listOf package;
91 default = [ ];
92 example = lib.literalExpression ''
93 [
94 pkgs.gvisor
95 ]
96 '';
97 description = lib.mdDoc ''
98 Extra packages to be installed in the Podman wrapper.
99 '';
100 };
101
102 autoPrune = {
103 enable = mkOption {
104 type = types.bool;
105 default = false;
106 description = lib.mdDoc ''
107 Whether to periodically prune Podman resources. If enabled, a
108 systemd timer will run `podman system prune -f`
109 as specified by the `dates` option.
110 '';
111 };
112
113 flags = mkOption {
114 type = types.listOf types.str;
115 default = [];
116 example = [ "--all" ];
117 description = lib.mdDoc ''
118 Any additional flags passed to {command}`podman system prune`.
119 '';
120 };
121
122 dates = mkOption {
123 default = "weekly";
124 type = types.str;
125 description = lib.mdDoc ''
126 Specification (in the format described by
127 {manpage}`systemd.time(7)`) of the time at
128 which the prune will occur.
129 '';
130 };
131 };
132
133 package = lib.mkOption {
134 type = types.package;
135 default = podmanPackage;
136 internal = true;
137 description = lib.mdDoc ''
138 The final Podman package (including extra packages).
139 '';
140 };
141
142 defaultNetwork.settings = lib.mkOption {
143 type = json.type;
144 default = { };
145 example = lib.literalExpression "{ dns_enabled = true; }";
146 description = lib.mdDoc ''
147 Settings for podman's default network.
148 '';
149 };
150
151 };
152
153 config = lib.mkIf cfg.enable
154 {
155 environment.systemPackages = [ cfg.package ]
156 ++ lib.optional cfg.dockerCompat dockerCompat;
157
158 # https://github.com/containers/podman/blob/097cc6eb6dd8e598c0e8676d21267b4edb11e144/docs/tutorials/basic_networking.md#default-network
159 environment.etc."containers/networks/podman.json" = lib.mkIf (cfg.defaultNetwork.settings != { }) {
160 source = json.generate "podman.json" ({
161 dns_enabled = false;
162 driver = "bridge";
163 id = "0000000000000000000000000000000000000000000000000000000000000000";
164 internal = false;
165 ipam_options = { driver = "host-local"; };
166 ipv6_enabled = false;
167 name = "podman";
168 network_interface = "podman0";
169 subnets = [{ gateway = "10.88.0.1"; subnet = "10.88.0.0/16"; }];
170 } // cfg.defaultNetwork.settings);
171 };
172
173 virtualisation.containers = {
174 enable = true; # Enable common /etc/containers configuration
175 containersConf.settings = {
176 network.network_backend = "netavark";
177 } // lib.optionalAttrs cfg.enableNvidia {
178 engine = {
179 conmon_env_vars = [ "PATH=${lib.makeBinPath [ pkgs.nvidia-podman ]}" ];
180 runtimes.nvidia = [ "${pkgs.nvidia-podman}/bin/nvidia-container-runtime" ];
181 };
182 };
183 };
184
185 systemd.packages = [ cfg.package ];
186
187 systemd.services.podman-prune = {
188 description = "Prune podman resources";
189
190 restartIfChanged = false;
191 unitConfig.X-StopOnRemoval = false;
192
193 serviceConfig.Type = "oneshot";
194
195 script = ''
196 ${cfg.package}/bin/podman system prune -f ${toString cfg.autoPrune.flags}
197 '';
198
199 startAt = lib.optional cfg.autoPrune.enable cfg.autoPrune.dates;
200 after = [ "podman.service" ];
201 requires = [ "podman.service" ];
202 };
203
204 systemd.sockets.podman.wantedBy = [ "sockets.target" ];
205 systemd.sockets.podman.socketConfig.SocketGroup = "podman";
206
207 systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
208
209 systemd.tmpfiles.packages = [
210 # The /run/podman rule interferes with our podman group, so we remove
211 # it and let the systemd socket logic take care of it.
212 (pkgs.runCommand "podman-tmpfiles-nixos" { package = cfg.package; } ''
213 mkdir -p $out/lib/tmpfiles.d/
214 grep -v 'D! /run/podman 0700 root root' \
215 <$package/lib/tmpfiles.d/podman.conf \
216 >$out/lib/tmpfiles.d/podman.conf
217 '')
218 ];
219
220 systemd.tmpfiles.rules =
221 lib.optionals cfg.dockerSocket.enable [
222 "L! /run/docker.sock - - - - /run/podman/podman.sock"
223 ];
224
225 users.groups.podman = { };
226
227 assertions = [
228 {
229 assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
230 message = "Option dockerCompat conflicts with docker";
231 }
232 {
233 assertion = cfg.dockerSocket.enable -> !config.virtualisation.docker.enable;
234 message = ''
235 The options virtualisation.podman.dockerSocket.enable and virtualisation.docker.enable conflict, because only one can serve the socket.
236 '';
237 }
238 ];
239 };
240}