2023-03-14 20:58:04 +03:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
cfg = config.virtualisation.docker;
|
|
|
|
|
|
|
|
|
|
proxyEnv = config.networking.proxy.envVars;
|
|
|
|
|
|
|
|
|
|
serviceOptions = { ... }: {
|
|
|
|
|
options = with lib; {
|
|
|
|
|
image = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
description = lib.mdDoc "OCI image to run.";
|
|
|
|
|
example = "library/hello-world";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
command = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [ ];
|
|
|
|
|
description = lib.mdDoc "Overrides the default command declared by the image (i.e. by Dockerfile's CMD).";
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"--port=9000"
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ports = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [ ];
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
Network ports to publish from the container to the outer host.
|
|
|
|
|
Valid formats:
|
|
|
|
|
- `<ip>:<hostPort>:<containerPort>`
|
|
|
|
|
- `<ip>::<containerPort>`
|
|
|
|
|
- `<hostPort>:<containerPort>`
|
|
|
|
|
- `<containerPort>`
|
|
|
|
|
Both `hostPort` and `containerPort` 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: `1234-1236:1234-1236/tcp`
|
|
|
|
|
When specifying a range for `hostPort` only, the `containerPort`
|
|
|
|
|
must *not* be a range. In this case, the container port is published
|
|
|
|
|
somewhere within the specified `hostPort` range.
|
|
|
|
|
Example: `1234-1236:1234/tcp`
|
|
|
|
|
Refer to the
|
|
|
|
|
[Docker engine documentation](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) for full details.
|
|
|
|
|
'';
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"8080:9000"
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
volumes = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [ ];
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
List of volumes to attach to this container.
|
|
|
|
|
Note that this is a list of `"src:dst"` strings to
|
|
|
|
|
allow for `src` to refer to `/nix/store` paths, which
|
|
|
|
|
would be difficult with an attribute set. There are
|
|
|
|
|
also a variety of mount options available as a third
|
|
|
|
|
field; please refer to the
|
|
|
|
|
[docker engine documentation](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) for details.
|
|
|
|
|
'';
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"volume_name:/path/inside/container"
|
|
|
|
|
"/path/on/host:/path/inside/container"
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
networks = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [ ];
|
2023-03-14 23:24:03 +03:00
|
|
|
|
description = lib.mdDoc "Networks to join.";
|
2023-03-14 20:58:04 +03:00
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"backend_internal"
|
|
|
|
|
"traefik_public"
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
extra_hosts = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [ ];
|
2023-03-14 23:24:03 +03:00
|
|
|
|
description = lib.mdDoc "Add hostname mappings.";
|
2023-03-14 20:58:04 +03:00
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"host.docker.internal:host-gateway"
|
2023-03-14 23:24:03 +03:00
|
|
|
|
"otherhost:50.31.209.229"
|
2023-03-14 20:58:04 +03:00
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
2023-03-14 23:11:28 +03:00
|
|
|
|
|
|
|
|
|
deploy = {
|
2023-03-14 23:24:03 +03:00
|
|
|
|
labels = {
|
|
|
|
|
default = [ ];
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
description = lib.mdDoc "Specify labels for the service.";
|
|
|
|
|
example = literalExpression ''
|
|
|
|
|
[
|
|
|
|
|
"com.example.description=This label will appear on the web service"
|
|
|
|
|
]
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-14 23:11:28 +03:00
|
|
|
|
placement = {
|
|
|
|
|
constraints = mkOption {
|
|
|
|
|
default = [ ];
|
|
|
|
|
type = types.listOf types.str;
|
2023-03-14 23:24:03 +03:00
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
You can limit the set of nodes where a task can be scheduled by defining constraint expressions.
|
|
|
|
|
Constraint expressions can either use a match (==) or exclude (!=) rule.
|
|
|
|
|
Multiple constraints find nodes that satisfy every expression (AND match).
|
|
|
|
|
'';
|
|
|
|
|
example = literalExample ''
|
|
|
|
|
[
|
|
|
|
|
"node.role==manager"
|
|
|
|
|
];
|
|
|
|
|
'';
|
2023-03-14 23:11:28 +03:00
|
|
|
|
};
|
|
|
|
|
};
|
2023-03-14 23:24:03 +03:00
|
|
|
|
|
2023-03-14 23:11:28 +03:00
|
|
|
|
update_config = {
|
|
|
|
|
order = mkOption {
|
|
|
|
|
default = "stop-first";
|
|
|
|
|
type = types.str;
|
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
Order of operations during updates.
|
|
|
|
|
- stop-first (old task is stopped before starting new one),
|
|
|
|
|
- start-first (new task is started first, and the running tasks briefly overlap)
|
|
|
|
|
|
|
|
|
|
Note: Only supported for v3.4 and higher.
|
|
|
|
|
'';
|
|
|
|
|
example = "start-first";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2023-03-14 20:58:04 +03:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
networkOptions = { ... }: {
|
|
|
|
|
options = with lib; {
|
|
|
|
|
external = mkOption {
|
2023-03-14 23:24:03 +03:00
|
|
|
|
default = false;
|
2023-03-14 20:58:04 +03:00
|
|
|
|
type = types.nullOr types.bool;
|
2023-03-14 23:24:03 +03:00
|
|
|
|
description = lib.mdDoc ''
|
|
|
|
|
If set to true, specifies that this volume has been created outside of Compose.
|
|
|
|
|
The systemd service does not attempt to create it, and raises an error if it doesn’t exist.
|
|
|
|
|
'';
|
2023-03-14 20:58:04 +03:00
|
|
|
|
example = "true";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
stackOptions = { ... }: {
|
|
|
|
|
options = with lib; {
|
|
|
|
|
version = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = "3.8";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
services = mkOption {
|
|
|
|
|
default = { };
|
|
|
|
|
type = types.attrsOf (types.submodule serviceOptions);
|
|
|
|
|
description = lib.mdDoc "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
networks = mkOption {
|
|
|
|
|
default = { };
|
|
|
|
|
type = types.attrsOf (types.submodule networkOptions);
|
|
|
|
|
description = lib.mdDoc "";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
mkComposeFile = stack:
|
|
|
|
|
pkgs.runCommand "compose.yml"
|
|
|
|
|
{
|
|
|
|
|
buildInputs = [ pkgs.remarshal ];
|
|
|
|
|
preferLocalBuild = true;
|
|
|
|
|
}
|
|
|
|
|
''
|
|
|
|
|
remarshal -if json -of yaml \
|
|
|
|
|
< ${ pkgs.writeText "compose.json" (builtins.toJSON stack)} \
|
|
|
|
|
> $out
|
|
|
|
|
'';
|
|
|
|
|
|
2023-03-14 23:11:42 +03:00
|
|
|
|
mkStackTimer = stackName: {
|
|
|
|
|
wantedBy = [ "timers.target" ];
|
|
|
|
|
timerConfig = {
|
|
|
|
|
OnBootSec = "5m";
|
|
|
|
|
OnUnitActiveSec = "5m";
|
|
|
|
|
Unit = "docker-stack-${stackName}.service";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-14 20:58:04 +03:00
|
|
|
|
mkStackService = stackName: stack:
|
|
|
|
|
let
|
|
|
|
|
escapedStackName = lib.escapeShellArg stackName;
|
|
|
|
|
composeFile = mkComposeFile stack;
|
|
|
|
|
in
|
|
|
|
|
{
|
|
|
|
|
description = "Deploy ${escapedStackName} stack";
|
2023-03-14 23:11:42 +03:00
|
|
|
|
enable = true;
|
2023-03-14 20:58:04 +03:00
|
|
|
|
|
|
|
|
|
after = [ "docker.service" "docker.socket" ];
|
|
|
|
|
environment = proxyEnv;
|
|
|
|
|
|
|
|
|
|
path = [ config.virtualisation.docker.package ];
|
|
|
|
|
|
|
|
|
|
script = lib.concatStringsSep " \\\n " ([
|
|
|
|
|
"exec docker stack deploy"
|
|
|
|
|
"--compose-file=${composeFile}"
|
|
|
|
|
escapedStackName
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
serviceConfig = {
|
|
|
|
|
Type = "oneshot";
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
options.virtualisation.docker.stacks = with lib; mkOption {
|
|
|
|
|
default = { };
|
|
|
|
|
type = types.attrsOf (types.submodule stackOptions);
|
|
|
|
|
description = lib.mdDoc "Docker stacks to deploy using systemd services.";
|
|
|
|
|
};
|
|
|
|
|
|
2023-03-15 22:51:19 +03:00
|
|
|
|
config = lib.mkIf (cfg.stacks != { }) {
|
2023-03-14 23:11:42 +03:00
|
|
|
|
systemd.timers = lib.mapAttrs' (n: v: lib.nameValuePair "docker-stack-${n}" (mkStackTimer n)) cfg.stacks;
|
2023-03-14 20:58:04 +03:00
|
|
|
|
systemd.services = lib.mapAttrs' (n: v: lib.nameValuePair "docker-stack-${n}" (mkStackService n v)) cfg.stacks;
|
|
|
|
|
|
|
|
|
|
virtualisation.docker = {
|
|
|
|
|
enable = true;
|
|
|
|
|
liveRestore = false;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|