1# Systemd services for docker.
2
3{
4 config,
5 lib,
6 utils,
7 pkgs,
8 ...
9}:
10
11with lib;
12
13let
14
15 cfg = config.virtualisation.docker;
16 proxy_env = config.networking.proxy.envVars;
17 settingsFormat = pkgs.formats.json { };
18 daemonSettingsFile = settingsFormat.generate "daemon.json" cfg.daemon.settings;
19in
20
21{
22 ###### interface
23
24 options.virtualisation.docker = {
25 enable = mkOption {
26 type = types.bool;
27 default = false;
28 description = ''
29 This option enables docker, a daemon that manages
30 linux containers. Users in the "docker" group can interact with
31 the daemon (e.g. to start or stop containers) using the
32 {command}`docker` command line tool.
33 '';
34 };
35
36 listenOptions = mkOption {
37 type = types.listOf types.str;
38 default = [ "/run/docker.sock" ];
39 description = ''
40 A list of unix and tcp docker should listen to. The format follows
41 ListenStream as described in {manpage}`systemd.socket(5)`.
42 '';
43 };
44
45 enableOnBoot = mkOption {
46 type = types.bool;
47 default = true;
48 description = ''
49 When enabled dockerd is started on boot. This is required for
50 containers which are created with the
51 `--restart=always` flag to work. If this option is
52 disabled, docker might be started on demand by socket activation.
53 '';
54 };
55
56 daemon.settings = mkOption {
57 type = types.submodule {
58 freeformType = settingsFormat.type;
59 options = {
60 live-restore = mkOption {
61 type = types.bool;
62 # Prior to NixOS 24.11, this was set to true by default, while upstream defaulted to false.
63 # Keep the option unset to follow upstream defaults
64 default = versionOlder config.system.stateVersion "24.11";
65 defaultText = literalExpression "lib.versionOlder config.system.stateVersion \"24.11\"";
66 description = ''
67 Allow dockerd to be restarted without affecting running container.
68 This option is incompatible with docker swarm.
69 '';
70 };
71 };
72 };
73 default = { };
74 example = {
75 ipv6 = true;
76 "live-restore" = true;
77 "fixed-cidr-v6" = "fd00::/80";
78 };
79 description = ''
80 Configuration for docker daemon. The attributes are serialized to JSON used as daemon.conf.
81 See https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
82 '';
83 };
84
85 enableNvidia = mkOption {
86 type = types.bool;
87 default = false;
88 description = ''
89 **Deprecated**, please use hardware.nvidia-container-toolkit.enable instead.
90
91 Enable nvidia-docker wrapper, supporting NVIDIA GPUs inside docker containers.
92 '';
93 };
94
95 storageDriver = mkOption {
96 type = types.nullOr (
97 types.enum [
98 "aufs"
99 "btrfs"
100 "devicemapper"
101 "overlay"
102 "overlay2"
103 "zfs"
104 ]
105 );
106 default = null;
107 description = ''
108 This option determines which Docker
109 [storage driver](https://docs.docker.com/storage/storagedriver/select-storage-driver/)
110 to use.
111 By default it lets docker automatically choose the preferred storage
112 driver.
113 However, it is recommended to specify a storage driver explicitly, as
114 docker's default varies over versions.
115
116 ::: {.warning}
117 Changing the storage driver will cause any existing containers
118 and images to become inaccessible.
119 :::
120 '';
121 };
122
123 logDriver = mkOption {
124 type = types.enum [
125 "none"
126 "json-file"
127 "syslog"
128 "journald"
129 "gelf"
130 "fluentd"
131 "awslogs"
132 "splunk"
133 "etwlogs"
134 "gcplogs"
135 "local"
136 ];
137 default = "journald";
138 description = ''
139 This option determines which Docker log driver to use.
140 '';
141 };
142
143 extraOptions = mkOption {
144 type = types.separatedString " ";
145 default = "";
146 description = ''
147 The extra command-line options to pass to
148 {command}`docker` daemon.
149 '';
150 };
151
152 autoPrune = {
153 enable = mkOption {
154 type = types.bool;
155 default = false;
156 description = ''
157 Whether to periodically prune Docker resources. If enabled, a
158 systemd timer will run `docker system prune -f`
159 as specified by the `dates` option.
160 '';
161 };
162
163 flags = mkOption {
164 type = types.listOf types.str;
165 default = [ ];
166 example = [ "--all" ];
167 description = ''
168 Any additional flags passed to {command}`docker system prune`.
169 '';
170 };
171
172 dates = mkOption {
173 default = "weekly";
174 type = types.str;
175 description = ''
176 Specification (in the format described by
177 {manpage}`systemd.time(7)`) of the time at
178 which the prune will occur.
179 '';
180 };
181
182 randomizedDelaySec = mkOption {
183 default = "0";
184 type = types.singleLineStr;
185 example = "45min";
186 description = ''
187 Add a randomized delay before each auto prune.
188 The delay will be chosen between zero and this value.
189 This value must be a time span in the format specified by
190 {manpage}`systemd.time(7)`
191 '';
192 };
193
194 persistent = mkOption {
195 default = true;
196 type = types.bool;
197 example = false;
198 description = ''
199 Takes a boolean argument. If true, the time when the service
200 unit was last triggered is stored on disk. When the timer is
201 activated, the service unit is triggered immediately if it
202 would have been triggered at least once during the time when
203 the timer was inactive. Such triggering is nonetheless
204 subject to the delay imposed by RandomizedDelaySec=. This is
205 useful to catch up on missed runs of the service when the
206 system was powered down.
207 '';
208 };
209 };
210
211 package = mkPackageOption pkgs "docker" { };
212
213 extraPackages = mkOption {
214 type = types.listOf types.package;
215 default = [ ];
216 example = literalExpression "with pkgs; [ criu ]";
217 description = ''
218 Extra packages to add to PATH for the docker daemon process.
219 '';
220 };
221 };
222
223 imports = [
224 (mkRemovedOptionModule [
225 "virtualisation"
226 "docker"
227 "socketActivation"
228 ] "This option was removed and socket activation is now always active")
229 (mkAliasOptionModule
230 [ "virtualisation" "docker" "liveRestore" ]
231 [ "virtualisation" "docker" "daemon" "settings" "live-restore" ]
232 )
233 ];
234
235 ###### implementation
236
237 config = mkIf cfg.enable (mkMerge [
238 {
239 boot.kernelModules = [
240 "bridge"
241 "veth"
242 "br_netfilter"
243 "xt_nat"
244 ];
245 boot.kernel.sysctl = {
246 "net.ipv4.conf.all.forwarding" = mkOverride 98 true;
247 "net.ipv4.conf.default.forwarding" = mkOverride 98 true;
248 };
249 environment.systemPackages = [ cfg.package ] ++ optional cfg.enableNvidia pkgs.nvidia-docker;
250 users.groups.docker.gid = config.ids.gids.docker;
251 systemd.packages = [ cfg.package ];
252
253 # Docker 25.0.0 supports CDI by default
254 # (https://docs.docker.com/engine/release-notes/25.0/#new). Encourage
255 # moving to CDI as opposed to having deprecated runtime
256 # wrappers.
257 warnings =
258 lib.optionals (cfg.enableNvidia && (lib.strings.versionAtLeast cfg.package.version "25"))
259 [
260 ''
261 You have set virtualisation.docker.enableNvidia. This option is deprecated, please set hardware.nvidia-container-toolkit.enable instead.
262 ''
263 ];
264
265 systemd.services.docker = {
266 wantedBy = optional cfg.enableOnBoot "multi-user.target";
267 after = [
268 "network.target"
269 "docker.socket"
270 ];
271 requires = [ "docker.socket" ];
272 environment = proxy_env;
273 serviceConfig = {
274 Type = "notify";
275 ExecStart = [
276 ""
277 ''
278 ${cfg.package}/bin/dockerd \
279 --config-file=${daemonSettingsFile} \
280 ${cfg.extraOptions}
281 ''
282 ];
283 ExecReload = [
284 ""
285 "${pkgs.procps}/bin/kill -s HUP $MAINPID"
286 ];
287 };
288
289 path =
290 [ pkgs.kmod ]
291 ++ optional (cfg.storageDriver == "zfs") pkgs.zfs
292 ++ optional cfg.enableNvidia pkgs.nvidia-docker
293 ++ cfg.extraPackages;
294 };
295
296 systemd.sockets.docker = {
297 description = "Docker Socket for the API";
298 wantedBy = [ "sockets.target" ];
299 socketConfig = {
300 ListenStream = cfg.listenOptions;
301 SocketMode = "0660";
302 SocketUser = "root";
303 SocketGroup = "docker";
304 };
305 };
306
307 systemd.services.docker-prune = {
308 description = "Prune docker resources";
309
310 restartIfChanged = false;
311 unitConfig.X-StopOnRemoval = false;
312
313 serviceConfig = {
314 Type = "oneshot";
315 ExecStart = utils.escapeSystemdExecArgs (
316 [
317 (lib.getExe cfg.package)
318 "system"
319 "prune"
320 "-f"
321 ]
322 ++ cfg.autoPrune.flags
323 );
324 };
325
326 startAt = optional cfg.autoPrune.enable cfg.autoPrune.dates;
327 after = [ "docker.service" ];
328 requires = [ "docker.service" ];
329 };
330
331 systemd.timers.docker-prune = mkIf cfg.autoPrune.enable {
332 timerConfig = {
333 RandomizedDelaySec = cfg.autoPrune.randomizedDelaySec;
334 Persistent = cfg.autoPrune.persistent;
335 };
336 };
337
338 assertions = [
339 {
340 assertion =
341 cfg.enableNvidia && pkgs.stdenv.hostPlatform.isx86_64
342 -> config.hardware.graphics.enable32Bit or false;
343 message = "Option enableNvidia on x86_64 requires 32-bit support libraries";
344 }
345 ];
346
347 virtualisation.docker.daemon.settings = {
348 group = "docker";
349 hosts = [ "fd://" ];
350 log-driver = mkDefault cfg.logDriver;
351 storage-driver = mkIf (cfg.storageDriver != null) (mkDefault cfg.storageDriver);
352 runtimes = mkIf cfg.enableNvidia {
353 nvidia = {
354 # Use the legacy nvidia-container-runtime wrapper to allow
355 # the `--runtime=nvidia` approach to expose
356 # GPU's. Starting with Docker > 25, CDI can be used
357 # instead, removing the need for runtime wrappers.
358 path = lib.getExe' pkgs.nvidia-docker "nvidia-container-runtime.legacy";
359 };
360 };
361 };
362 }
363 ]);
364}