1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.dockerRegistry;
7
8 blobCache = if cfg.enableRedisCache
9 then "redis"
10 else "inmemory";
11
12 registryConfig = {
13 version = "0.1";
14 log.fields.service = "registry";
15 storage = {
16 cache.blobdescriptor = blobCache;
17 filesystem.rootdirectory = cfg.storagePath;
18 delete.enabled = cfg.enableDelete;
19 };
20 http = {
21 addr = ":${builtins.toString cfg.port}";
22 headers.X-Content-Type-Options = ["nosniff"];
23 };
24 health.storagedriver = {
25 enabled = true;
26 interval = "10s";
27 threshold = 3;
28 };
29 };
30
31 registryConfig.redis = mkIf cfg.enableRedisCache {
32 addr = "${cfg.redisUrl}";
33 password = "${cfg.redisPassword}";
34 db = 0;
35 dialtimeout = "10ms";
36 readtimeout = "10ms";
37 writetimeout = "10ms";
38 pool = {
39 maxidle = 16;
40 maxactive = 64;
41 idletimeout = "300s";
42 };
43 };
44
45 configFile = pkgs.writeText "docker-registry-config.yml" (builtins.toJSON (recursiveUpdate registryConfig cfg.extraConfig));
46
47in {
48 options.services.dockerRegistry = {
49 enable = mkEnableOption "Docker Registry";
50
51 listenAddress = mkOption {
52 description = "Docker registry host or ip to bind to.";
53 default = "127.0.0.1";
54 type = types.str;
55 };
56
57 port = mkOption {
58 description = "Docker registry port to bind to.";
59 default = 5000;
60 type = types.int;
61 };
62
63 storagePath = mkOption {
64 type = types.path;
65 default = "/var/lib/docker-registry";
66 description = "Docker registry storage path.";
67 };
68
69 enableDelete = mkOption {
70 type = types.bool;
71 default = false;
72 description = "Enable delete for manifests and blobs.";
73 };
74
75 enableRedisCache = mkEnableOption "redis as blob cache";
76
77 redisUrl = mkOption {
78 type = types.str;
79 default = "localhost:6379";
80 description = "Set redis host and port.";
81 };
82
83 redisPassword = mkOption {
84 type = types.str;
85 default = "";
86 description = "Set redis password.";
87 };
88
89 extraConfig = mkOption {
90 description = ''
91 Docker extra registry configuration via environment variables.
92 '';
93 default = {};
94 type = types.attrs;
95 };
96
97 enableGarbageCollect = mkEnableOption "garbage collect";
98
99 garbageCollectDates = mkOption {
100 default = "daily";
101 type = types.str;
102 description = ''
103 Specification (in the format described by
104 <citerefentry><refentrytitle>systemd.time</refentrytitle>
105 <manvolnum>7</manvolnum></citerefentry>) of the time at
106 which the garbage collect will occur.
107 '';
108 };
109 };
110
111 config = mkIf cfg.enable {
112 systemd.services.docker-registry = {
113 description = "Docker Container Registry";
114 wantedBy = [ "multi-user.target" ];
115 after = [ "network.target" ];
116 script = ''
117 ${pkgs.docker-distribution}/bin/registry serve ${configFile}
118 '';
119
120 serviceConfig = {
121 User = "docker-registry";
122 WorkingDirectory = cfg.storagePath;
123 AmbientCapabilities = mkIf (cfg.port < 1024) "cap_net_bind_service";
124 };
125 };
126
127 systemd.services.docker-registry-garbage-collect = {
128 description = "Run Garbage Collection for docker registry";
129
130 restartIfChanged = false;
131 unitConfig.X-StopOnRemoval = false;
132
133 serviceConfig.Type = "oneshot";
134
135 script = ''
136 ${pkgs.docker-distribution}/bin/registry garbage-collect ${configFile}
137 ${pkgs.systemd}/bin/systemctl restart docker-registry.service
138 '';
139
140 startAt = optional cfg.enableGarbageCollect cfg.garbageCollectDates;
141 };
142
143 users.users.docker-registry = {
144 createHome = true;
145 home = cfg.storagePath;
146 };
147 };
148}