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