add docker-stack nixos module and deploy traefik

This commit is contained in:
Dmitriy Pleshevskiy 2023-03-14 20:58:04 +03:00
parent d7dfeb35fd
commit c5bba581f5
Signed by: pleshevskiy
GPG key ID: 79C4487B44403985
4 changed files with 197 additions and 14 deletions

View file

@ -41,6 +41,7 @@ in
extraModules = [ extraModules = [
inputs.mailserver.nixosModule inputs.mailserver.nixosModule
./modules/docker-stack.nix
]; ];
}; };

View file

@ -23,11 +23,11 @@ let
staticConfigFile = pkgs.runCommand "config.toml" staticConfigFile = pkgs.runCommand "config.toml"
{ {
buildInputs = [ pkgs.yj ]; buildInputs = [ pkgs.remarshal ];
preferLocalBuild = true; preferLocalBuild = true;
} }
'' ''
yj -jt -i \ remarshal -if json -of toml \
< ${ < ${
pkgs.writeText "static_config.json" (builtins.toJSON pkgs.writeText "static_config.json" (builtins.toJSON
(lib.recursiveUpdate traefikCfg.staticConfigOptions { (lib.recursiveUpdate traefikCfg.staticConfigOptions {
@ -59,25 +59,20 @@ in
inherit group; inherit group;
}; };
virtualisation.oci-containers = { virtualisation.docker.stacks.traefik = {
backend = "docker"; networks.traefik_public.external = true;
containers.traefik = { services.traefik = {
image = "traefik:v2.9"; image = "traefik:v2.9";
cmd = [ command = [
"--configFile=${staticConfigFile}" "--configFile=${staticConfigFile}"
]; ];
extraOptions = [
# enable host.docker.internal
"--add-host=host.docker.internal:host-gateway"
# attach to overlay network. To create, run the following command:
# docker network create --driver=overlay --attachable traefik_public
"--network=traefik_public"
];
ports = [ ports = [
"80:80" "80:80"
"443:443" "443:443"
"8080:8080" "8080:8080"
]; ];
extra_hosts = [ "host.docker.internal:host-gateway" ];
networks = [ "traefik_public" ];
volumes = [ volumes = [
"${mirrorVolume "/var/run/docker.sock"}:ro" "${mirrorVolume "/var/run/docker.sock"}:ro"
"${mirrorVolume dataDir}" "${mirrorVolume dataDir}"

View file

@ -0,0 +1,187 @@
{ 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 = [ ];
description = lib.mdDoc ''
'';
example = literalExpression ''
[
"backend_internal"
"traefik_public"
]
'';
};
extra_hosts = mkOption {
type = types.listOf types.str;
default = [ ];
description = lib.mdDoc ''
'';
example = literalExpression ''
[
"host.docker.internal:host-gateway"
]
'';
};
};
};
networkOptions = { ... }: {
options = with lib; {
external = mkOption {
default = null;
type = types.nullOr types.bool;
description = lib.mdDoc "";
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
'';
mkStackService = stackName: stack:
let
escapedStackName = lib.escapeShellArg stackName;
composeFile = mkComposeFile stack;
in
{
description = "Deploy ${escapedStackName} stack";
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.";
};
config = lib.mkIf (cfg.stacks != { }) {
systemd.services = lib.mapAttrs' (n: v: lib.nameValuePair "docker-stack-${n}" (mkStackService n v)) cfg.stacks;
virtualisation.docker = {
enable = true;
liveRestore = false;
};
};
}

View file

@ -4,7 +4,7 @@
nix.gc = { nix.gc = {
automatic = true; automatic = true;
dates = "weekly"; dates = "weekly";
options = "--delete-older-than 7d"; options = "--delete-older-than 14d";
}; };
virtualisation.docker.autoPrune = { virtualisation.docker.autoPrune = {