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 description = ''
107 Extra dependencies for podman to be placed on $PATH in the wrapper.
108 '';
109 };
110
111 extraRuntimes = mkOption {
112 type = with types; listOf package;
113 # keep the default in sync with the podman package
114 default = lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.runc ];
115 defaultText = lib.literalExpression ''lib.optionals pkgs.stdenv.hostPlatform.isLinux [ pkgs.runc ]'';
116 example = lib.literalExpression ''
117 [
118 pkgs.gvisor
119 ]
120 '';
121 description = ''
122 Extra runtime packages to be installed in the Podman wrapper.
123 Those are then placed in libexec/podman, i.e. are seen as podman internal commands.
124 '';
125 };
126
127 autoPrune = {
128 enable = mkOption {
129 type = types.bool;
130 default = false;
131 description = ''
132 Whether to periodically prune Podman resources. If enabled, a
133 systemd timer will run `podman system prune -f`
134 as specified by the `dates` option.
135 '';
136 };
137
138 flags = mkOption {
139 type = types.listOf types.str;
140 default = [ ];
141 example = [ "--all" ];
142 description = ''
143 Any additional flags passed to {command}`podman system prune`.
144 '';
145 };
146
147 dates = mkOption {
148 default = "weekly";
149 type = types.str;
150 description = ''
151 Specification (in the format described by
152 {manpage}`systemd.time(7)`) of the time at
153 which the prune will occur.
154 '';
155 };
156 };
157
158 package =
159 (lib.mkPackageOption pkgs "podman" {
160 extraDescription = ''
161 This package will automatically include extra packages and runtimes.
162 '';
163 })
164 // {
165 apply =
166 pkg:
167 pkg.override {
168 extraPackages =
169 cfg.extraPackages
170 ++ [
171 "/run/wrappers" # setuid shadow
172 config.systemd.package # To allow systemd-based container healthchecks
173 ]
174 ++ lib.optional (config.boot.supportedFilesystems.zfs or false) config.boot.zfs.package;
175 extraRuntimes =
176 cfg.extraRuntimes
177 ++
178 lib.optionals
179 (
180 config.virtualisation.containers.containersConf.settings.network.default_rootless_network_cmd or ""
181 == "slirp4netns"
182 )
183 (
184 with pkgs;
185 [
186 slirp4netns
187 ]
188 );
189 };
190 };
191
192 defaultNetwork.settings = lib.mkOption {
193 type = json.type;
194 default = { };
195 example = lib.literalExpression "{ dns_enabled = true; }";
196 description = ''
197 Settings for podman's default network.
198 '';
199 };
200
201 };
202
203 config =
204 let
205 networkConfig = (
206 {
207 dns_enabled = false;
208 driver = "bridge";
209 id = "0000000000000000000000000000000000000000000000000000000000000000";
210 internal = false;
211 ipam_options = {
212 driver = "host-local";
213 };
214 ipv6_enabled = false;
215 name = "podman";
216 network_interface = "podman0";
217 subnets = [
218 {
219 gateway = "10.88.0.1";
220 subnet = "10.88.0.0/16";
221 }
222 ];
223 }
224 // cfg.defaultNetwork.settings
225 );
226 inherit (networkConfig) dns_enabled network_interface;
227 in
228 lib.mkIf cfg.enable {
229 warnings = lib.optionals cfg.enableNvidia [
230 ''
231 You have set virtualisation.podman.enableNvidia. This option is deprecated, please set hardware.nvidia-container-toolkit.enable instead.
232 ''
233 ];
234
235 environment.systemPackages = [ cfg.package ] ++ lib.optional cfg.dockerCompat dockerCompat;
236
237 # https://github.com/containers/podman/blob/097cc6eb6dd8e598c0e8676d21267b4edb11e144/docs/tutorials/basic_networking.md#default-network
238 environment.etc."containers/networks/podman.json" = lib.mkIf (cfg.defaultNetwork.settings != { }) {
239 source = json.generate "podman.json" networkConfig;
240 };
241
242 # containers cannot reach aardvark-dns otherwise
243 networking.firewall.interfaces.${network_interface}.allowedUDPPorts = lib.mkIf dns_enabled [ 53 ];
244
245 virtualisation.containers = {
246 enable = true; # Enable common /etc/containers configuration
247 containersConf.settings = {
248 network = {
249 network_backend = "netavark";
250 firewall_driver = lib.mkIf config.networking.nftables.enable "nftables";
251 };
252 };
253 };
254
255 systemd.packages = [ cfg.package ];
256
257 systemd.services.podman-prune = {
258 description = "Prune podman resources";
259
260 restartIfChanged = false;
261 unitConfig.X-StopOnRemoval = false;
262
263 serviceConfig = {
264 Type = "oneshot";
265 ExecStart = utils.escapeSystemdExecArgs (
266 [
267 (lib.getExe cfg.package)
268 "system"
269 "prune"
270 "-f"
271 ]
272 ++ cfg.autoPrune.flags
273 );
274 };
275
276 startAt = lib.optional cfg.autoPrune.enable cfg.autoPrune.dates;
277 after = [ "podman.service" ];
278 requires = [ "podman.service" ];
279 };
280
281 systemd.services.podman.environment = config.networking.proxy.envVars;
282 systemd.sockets.podman.wantedBy = [ "sockets.target" ];
283 systemd.sockets.podman.socketConfig.SocketGroup = "podman";
284 # Podman does not support multiple sockets, as of podman 5.0.2, so we use
285 # a symlink. Unfortunately this does not let us use an alternate group,
286 # such as `docker`.
287 systemd.sockets.podman.socketConfig.Symlinks = lib.mkIf cfg.dockerSocket.enable [
288 "/run/docker.sock"
289 ];
290
291 systemd.user.services.podman.environment = config.networking.proxy.envVars;
292 systemd.user.sockets.podman.wantedBy = [ "sockets.target" ];
293
294 systemd.timers.podman-prune.timerConfig = lib.mkIf cfg.autoPrune.enable {
295 Persistent = true;
296 RandomizedDelaySec = 1800;
297 };
298
299 systemd.tmpfiles.packages = [
300 # The /run/podman rule interferes with our podman group, so we remove
301 # it and let the systemd socket logic take care of it.
302 (pkgs.runCommand "podman-tmpfiles-nixos"
303 {
304 package = cfg.package;
305 preferLocalBuild = true;
306 }
307 ''
308 mkdir -p $out/lib/tmpfiles.d/
309 grep -v 'D! /run/podman 0700 root root' \
310 <$package/lib/tmpfiles.d/podman.conf \
311 >$out/lib/tmpfiles.d/podman.conf
312 ''
313 )
314 ];
315
316 users.groups.podman = { };
317
318 assertions = [
319 {
320 assertion = cfg.dockerCompat -> !config.virtualisation.docker.enable;
321 message = "Option dockerCompat conflicts with docker";
322 }
323 {
324 assertion = cfg.dockerSocket.enable -> !config.virtualisation.docker.enable;
325 message = ''
326 The options virtualisation.podman.dockerSocket.enable and virtualisation.docker.enable conflict, because only one can serve the socket.
327 '';
328 }
329 ];
330 };
331}