1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.dockerRegistry;
9
10 blobCache = if cfg.enableRedisCache then "redis" else "inmemory";
11
12 registryConfig = {
13 version = "0.1";
14 log.fields.service = "registry";
15 storage = {
16 cache.blobdescriptor = blobCache;
17 delete.enabled = cfg.enableDelete;
18 } // (lib.optionalAttrs (cfg.storagePath != null) { filesystem.rootdirectory = cfg.storagePath; });
19 http = {
20 addr = "${cfg.listenAddress}:${builtins.toString cfg.port}";
21 headers.X-Content-Type-Options = [ "nosniff" ];
22 };
23 health.storagedriver = {
24 enabled = true;
25 interval = "10s";
26 threshold = 3;
27 };
28 };
29
30 registryConfig.redis = lib.mkIf cfg.enableRedisCache {
31 addr = "${cfg.redisUrl}";
32 password = "${cfg.redisPassword}";
33 db = 0;
34 dialtimeout = "10ms";
35 readtimeout = "10ms";
36 writetimeout = "10ms";
37 pool = {
38 maxidle = 16;
39 maxactive = 64;
40 idletimeout = "300s";
41 };
42 };
43
44 configFile = cfg.configFile;
45in
46{
47 options.services.dockerRegistry = {
48 enable = lib.mkEnableOption "Docker Registry";
49
50 package = lib.mkPackageOption pkgs "docker-distribution" {
51 example = "gitlab-container-registry";
52 };
53
54 listenAddress = lib.mkOption {
55 description = "Docker registry host or ip to bind to.";
56 default = "127.0.0.1";
57 type = lib.types.str;
58 };
59
60 port = lib.mkOption {
61 description = "Docker registry port to bind to.";
62 default = 5000;
63 type = lib.types.port;
64 };
65
66 openFirewall = lib.mkOption {
67 type = lib.types.bool;
68 default = false;
69 description = "Opens the port used by the firewall.";
70 };
71
72 storagePath = lib.mkOption {
73 type = lib.types.nullOr lib.types.path;
74 default = "/var/lib/docker-registry";
75 description = ''
76 Docker registry storage path for the filesystem storage backend. Set to
77 null to configure another backend via extraConfig.
78 '';
79 };
80
81 enableDelete = lib.mkOption {
82 type = lib.types.bool;
83 default = false;
84 description = "Enable delete for manifests and blobs.";
85 };
86
87 enableRedisCache = lib.mkEnableOption "redis as blob cache";
88
89 redisUrl = lib.mkOption {
90 type = lib.types.str;
91 default = "localhost:6379";
92 description = "Set redis host and port.";
93 };
94
95 redisPassword = lib.mkOption {
96 type = lib.types.str;
97 default = "";
98 description = "Set redis password.";
99 };
100
101 extraConfig = lib.mkOption {
102 description = ''
103 Docker extra registry configuration.
104 '';
105 example = lib.literalExpression ''
106 {
107 log.level = "debug";
108 }
109 '';
110 default = { };
111 type = lib.types.attrs;
112 };
113
114 configFile = lib.mkOption {
115 default = pkgs.writeText "docker-registry-config.yml" (
116 builtins.toJSON (lib.recursiveUpdate registryConfig cfg.extraConfig)
117 );
118 defaultText = lib.literalExpression ''pkgs.writeText "docker-registry-config.yml" "# my custom docker-registry-config.yml ..."'';
119 description = ''
120 Path to CNCF distribution config file.
121
122 Setting this option will override any configuration applied by the extraConfig option.
123 '';
124 type = lib.types.path;
125 };
126
127 enableGarbageCollect = lib.mkEnableOption "garbage collect";
128
129 garbageCollectDates = lib.mkOption {
130 default = "daily";
131 type = lib.types.str;
132 description = ''
133 Specification (in the format described by
134 {manpage}`systemd.time(7)`) of the time at
135 which the garbage collect will occur.
136 '';
137 };
138 };
139
140 config = lib.mkIf cfg.enable {
141 systemd.services.docker-registry = {
142 description = "Docker Container Registry";
143 wantedBy = [ "multi-user.target" ];
144 after = [ "network.target" ];
145 script = ''
146 ${cfg.package}/bin/registry serve ${configFile}
147 '';
148
149 serviceConfig = {
150 User = "docker-registry";
151 WorkingDirectory = cfg.storagePath;
152 AmbientCapabilities = lib.mkIf (cfg.port < 1024) "cap_net_bind_service";
153 };
154 };
155
156 systemd.services.docker-registry-garbage-collect = {
157 description = "Run Garbage Collection for docker registry";
158
159 restartIfChanged = false;
160 unitConfig.X-StopOnRemoval = false;
161
162 serviceConfig.Type = "oneshot";
163
164 script = ''
165 ${cfg.package}/bin/registry garbage-collect ${configFile}
166 /run/current-system/systemd/bin/systemctl restart docker-registry.service
167 '';
168
169 startAt = lib.optional cfg.enableGarbageCollect cfg.garbageCollectDates;
170 };
171
172 users.users.docker-registry =
173 (lib.optionalAttrs (cfg.storagePath != null) {
174 createHome = true;
175 home = cfg.storagePath;
176 })
177 // {
178 group = "docker-registry";
179 isSystemUser = true;
180 };
181 users.groups.docker-registry = { };
182
183 networking.firewall = lib.mkIf cfg.openFirewall {
184 allowedTCPPorts = [ cfg.port ];
185 };
186 };
187}