2019-03-24 22:59:09 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.docker-containers;
|
|
|
|
|
|
|
|
dockerContainer =
|
2019-06-16 20:59:06 +01:00
|
|
|
{ ... }: {
|
2019-03-24 22:59:09 +00:00
|
|
|
|
|
|
|
options = {
|
|
|
|
|
|
|
|
image = mkOption {
|
2020-01-28 21:00:54 +00:00
|
|
|
type = with types; str;
|
2019-03-24 22:59:09 +00:00
|
|
|
description = "Docker image to run.";
|
|
|
|
example = "library/hello-world";
|
|
|
|
};
|
|
|
|
|
2020-01-28 21:00:54 +00:00
|
|
|
imageFile = mkOption {
|
|
|
|
type = with types; nullOr package;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Path to an image file to load instead of pulling from a registry.
|
|
|
|
If defined, do not pull from registry.
|
|
|
|
|
|
|
|
You still need to set the <literal>image</literal> attribute, as it
|
|
|
|
will be used as the image name for docker to start a container.
|
|
|
|
'';
|
|
|
|
example = literalExample "pkgs.dockerTools.buildDockerImage {...};";
|
|
|
|
};
|
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
cmd = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = "Commandline arguments to pass to the image's entrypoint.";
|
|
|
|
example = literalExample ''
|
|
|
|
["--port=9000"]
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
entrypoint = mkOption {
|
|
|
|
type = with types; nullOr str;
|
2020-02-05 21:30:31 +00:00
|
|
|
description = "Override the default entrypoint of the image.";
|
2019-03-24 22:59:09 +00:00
|
|
|
default = null;
|
|
|
|
example = "/bin/my-app";
|
|
|
|
};
|
|
|
|
|
|
|
|
environment = mkOption {
|
|
|
|
type = with types; attrsOf str;
|
|
|
|
default = {};
|
|
|
|
description = "Environment variables to set for this container.";
|
|
|
|
example = literalExample ''
|
|
|
|
{
|
|
|
|
DATABASE_HOST = "db.example.com";
|
|
|
|
DATABASE_PORT = "3306";
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
log-driver = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
default = "none";
|
|
|
|
description = ''
|
|
|
|
Logging driver for the container. The default of
|
|
|
|
<literal>"none"</literal> means that the container's logs will be
|
|
|
|
handled as part of the systemd unit. Setting this to
|
|
|
|
<literal>"journald"</literal> will result in duplicate logging, but
|
|
|
|
the container's logs will be visible to the <command>docker
|
|
|
|
logs</command> command.
|
|
|
|
|
|
|
|
For more details and a full list of logging drivers, refer to the
|
|
|
|
<link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver">
|
|
|
|
Docker engine documentation</link>
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
ports = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = ''
|
|
|
|
Network ports to publish from the container to the outer host.
|
2019-05-13 08:15:17 +01:00
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
Valid formats:
|
2019-05-13 08:15:17 +01:00
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal><ip>:<hostPort>:<containerPort></literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal><ip>::<containerPort></literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal><hostPort>:<containerPort></literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
<listitem>
|
|
|
|
<para>
|
|
|
|
<literal><containerPort></literal>
|
|
|
|
</para>
|
|
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
2019-05-13 08:15:17 +01:00
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
Both <literal>hostPort</literal> and
|
|
|
|
<literal>containerPort</literal> can be specified as a range of
|
|
|
|
ports. When specifying ranges for both, the number of container
|
|
|
|
ports in the range must match the number of host ports in the
|
|
|
|
range. Example: <literal>1234-1236:1234-1236/tcp</literal>
|
2019-05-13 08:15:17 +01:00
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
When specifying a range for <literal>hostPort</literal> only, the
|
|
|
|
<literal>containerPort</literal> must <emphasis>not</emphasis> be a
|
|
|
|
range. In this case, the container port is published somewhere
|
|
|
|
within the specified <literal>hostPort</literal> range. Example:
|
|
|
|
<literal>1234-1236:1234/tcp</literal>
|
2019-05-13 08:15:17 +01:00
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
Refer to the
|
|
|
|
<link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
|
|
|
|
Docker engine documentation</link> for full details.
|
|
|
|
'';
|
|
|
|
example = literalExample ''
|
|
|
|
[
|
|
|
|
"8080:9000"
|
|
|
|
]
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
user = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Override the username or UID (and optionally groupname or GID) used
|
|
|
|
in the container.
|
|
|
|
'';
|
|
|
|
example = "nobody:nogroup";
|
|
|
|
};
|
|
|
|
|
|
|
|
volumes = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = ''
|
|
|
|
List of volumes to attach to this container.
|
|
|
|
|
|
|
|
Note that this is a list of <literal>"src:dst"</literal> strings to
|
|
|
|
allow for <literal>src</literal> to refer to
|
2020-02-05 21:30:31 +00:00
|
|
|
<literal>/nix/store</literal> paths, which would be difficult with an
|
2019-03-24 22:59:09 +00:00
|
|
|
attribute set. There are also a variety of mount options available
|
|
|
|
as a third field; please refer to the
|
|
|
|
<link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems">
|
|
|
|
docker engine documentation</link> for details.
|
|
|
|
'';
|
|
|
|
example = literalExample ''
|
|
|
|
[
|
|
|
|
"volume_name:/path/inside/container"
|
|
|
|
"/path/on/host:/path/inside/container"
|
|
|
|
]
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
workdir = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
description = "Override the default working directory for the container.";
|
|
|
|
example = "/var/lib/hello_world";
|
|
|
|
};
|
|
|
|
|
2020-01-28 21:00:54 +00:00
|
|
|
dependsOn = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = ''
|
|
|
|
Define which other containers this one depends on. They will be added to both After and Requires for the unit.
|
|
|
|
|
|
|
|
Use the same name as the attribute under <literal>services.docker-containers</literal>.
|
|
|
|
'';
|
|
|
|
example = literalExample ''
|
|
|
|
services.docker-containers = {
|
|
|
|
node1 = {};
|
|
|
|
node2 = {
|
|
|
|
dependsOn = [ "node1" ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
extraDockerOptions = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = "Extra options for <command>docker run</command>.";
|
|
|
|
example = literalExample ''
|
|
|
|
["--network=host"]
|
|
|
|
'';
|
|
|
|
};
|
2020-02-14 22:57:31 +00:00
|
|
|
|
|
|
|
autoStart = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
When enabled, the container is automatically started on boot.
|
|
|
|
If this option is set to false, the container has to be started on-demand via its service.
|
|
|
|
'';
|
|
|
|
};
|
2019-03-24 22:59:09 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2020-01-28 21:00:54 +00:00
|
|
|
mkService = name: container: let
|
|
|
|
mkAfter = map (x: "docker-${x}.service") container.dependsOn;
|
|
|
|
in rec {
|
2020-02-14 22:57:31 +00:00
|
|
|
wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
|
2020-01-28 21:00:54 +00:00
|
|
|
after = [ "docker.service" "docker.socket" ] ++ mkAfter;
|
|
|
|
requires = after;
|
2020-02-15 19:44:36 +00:00
|
|
|
path = [ pkgs.docker ];
|
|
|
|
|
|
|
|
preStart = ''
|
|
|
|
docker rm -f ${name} || true
|
|
|
|
${optionalString (container.imageFile != null) ''
|
|
|
|
docker load -i ${container.imageFile}
|
|
|
|
''}
|
|
|
|
'';
|
|
|
|
postStop = "docker rm -f ${name} || true";
|
|
|
|
|
2019-03-24 22:59:09 +00:00
|
|
|
serviceConfig = {
|
|
|
|
ExecStart = concatStringsSep " \\\n " ([
|
|
|
|
"${pkgs.docker}/bin/docker run"
|
|
|
|
"--rm"
|
2020-01-28 21:00:54 +00:00
|
|
|
"--name=${name}"
|
2019-03-24 22:59:09 +00:00
|
|
|
"--log-driver=${container.log-driver}"
|
2019-04-24 04:48:22 +01:00
|
|
|
] ++ optional (container.entrypoint != null)
|
2019-03-24 22:59:09 +00:00
|
|
|
"--entrypoint=${escapeShellArg container.entrypoint}"
|
|
|
|
++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
|
|
|
|
++ map (p: "-p ${escapeShellArg p}") container.ports
|
2019-04-24 04:48:22 +01:00
|
|
|
++ optional (container.user != null) "-u ${escapeShellArg container.user}"
|
2019-03-24 22:59:09 +00:00
|
|
|
++ map (v: "-v ${escapeShellArg v}") container.volumes
|
2019-04-24 04:48:22 +01:00
|
|
|
++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
|
2019-03-24 22:59:09 +00:00
|
|
|
++ map escapeShellArg container.extraDockerOptions
|
|
|
|
++ [container.image]
|
|
|
|
++ map escapeShellArg container.cmd
|
|
|
|
);
|
2020-01-28 21:00:54 +00:00
|
|
|
|
2020-02-15 19:44:36 +00:00
|
|
|
ExecStop = ''${pkgs.bash}/bin/sh -c "[ $SERVICE_RESULT = success ] || docker stop ${name}"'';
|
2019-03-24 22:59:09 +00:00
|
|
|
|
|
|
|
### There is no generalized way of supporting `reload` for docker
|
|
|
|
### containers. Some containers may respond well to SIGHUP sent to their
|
|
|
|
### init process, but it is not guaranteed; some apps have other reload
|
|
|
|
### mechanisms, some don't have a reload signal at all, and some docker
|
|
|
|
### images just have broken signal handling. The best compromise in this
|
|
|
|
### case is probably to leave ExecReload undefined, so `systemctl reload`
|
|
|
|
### will at least result in an error instead of potentially undefined
|
|
|
|
### behaviour.
|
|
|
|
###
|
|
|
|
### Advanced users can still override this part of the unit to implement
|
|
|
|
### a custom reload handler, since the result of all this is a normal
|
|
|
|
### systemd service from the perspective of the NixOS module system.
|
|
|
|
###
|
|
|
|
# ExecReload = ...;
|
|
|
|
###
|
|
|
|
|
|
|
|
TimeoutStartSec = 0;
|
|
|
|
TimeoutStopSec = 120;
|
|
|
|
Restart = "always";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
in {
|
|
|
|
|
|
|
|
options.docker-containers = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = types.attrsOf (types.submodule dockerContainer);
|
|
|
|
description = "Docker containers to run as systemd services.";
|
|
|
|
};
|
|
|
|
|
2019-03-26 06:10:18 +00:00
|
|
|
config = mkIf (cfg != {}) {
|
2019-03-24 22:59:09 +00:00
|
|
|
|
|
|
|
systemd.services = mapAttrs' (n: v: nameValuePair "docker-${n}" (mkService n v)) cfg;
|
|
|
|
|
|
|
|
virtualisation.docker.enable = true;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|