user nginx; worker_processes auto; # error log config comes from external file created by entrypoint, to toggle debug on/off. include /etc/nginx/error.log.debug.warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { map_hash_bucket_size 128; include /etc/nginx/mime.types; default_type application/octet-stream; # Use a debug-oriented logging format. log_format debugging '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent ' '"HOST: $host" "UPSTREAM: $upstream_addr" ' '"UPSTREAM-STATUS: $upstream_status" ' '"SSL-PROTO: $ssl_protocol" ' '"CONNECT-HOST: $connect_host" "CONNECT-PORT: $connect_port" "CONNECT-ADDR: $connect_addr" ' '"PROXY-HOST: $proxy_host" "UPSTREAM-REDIRECT: $upstream_http_location" "CACHE-STATUS: $upstream_cache_status" ' '"AUTH: $http_authorization" ' ; log_format debug_proxy 'CONNECTPROXY: $remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent ' '"HOST: $host" "UPSTREAM: $upstream_addr" ' '"UPSTREAM-STATUS: $upstream_status" ' '"SSL-PROTO: $ssl_protocol" ' '"CONNECT-HOST: $connect_host" "CONNECT-PORT: $connect_port" "CONNECT-ADDR: $connect_addr" "INTERCEPTED: $interceptedHost" ' '"PROXY-HOST: $proxy_host" "UPSTREAM-REDIRECT: $upstream_http_location" "CACHE-STATUS: $upstream_cache_status" ' '"AUTH: $http_authorization" ' ; log_format tweaked '$upstream_cache_status [$time_local] "$uri" ' '$status $body_bytes_sent ' '"HOST:$host" ' '"PROXY-HOST:$proxy_host" "UPSTREAM:$upstream_addr" '; keepalive_timeout 300; gzip off; # The cache directory. This can get huge. Better to use a Docker volume pointing here! # Set to 32gb which should be enough proxy_cache_path /docker_mirror_cache levels=1:2 max_size=32g inactive=60d keys_zone=cache:10m use_temp_path=off; # Just in case you want to rewrite some hosts. Default maps directly. map $host $targetHost { hostnames; default $host; } # A map to enable authentication to some specific docker registries. # This is auto-generated by the entrypoint.sh based on environment variables map $host $dockerAuth { hostnames; include /etc/nginx/docker.auth.map; default ""; } # @TODO: actually for auth.docker.io, if we want to support multiple authentications, we'll need to decide # @TODO: based not only on the hostname, but also URI (/token) and query string (?scope) # @TODO: I wonder if this would help gcr.io and quay.io with authentication also.... map $dockerAuth $finalAuth { "" "$http_authorization"; # if empty, keep the original passed-in from the docker client. default "Basic $dockerAuth"; # if not empty, add the Basic preamble to the auth } # Map to decide which hosts get directed to the caching portion. # This is automatically generated from the list of cached registries, plus a few fixed hosts # By default, we don't intercept, allowing free flow of non-registry traffic map $connect_host $interceptedHost { hostnames; include /etc/nginx/docker.intercept.map; default "$connect_addr"; # $connect_addr is 'IP address and port of the remote host, e.g. "192.168.1.5:12345". IP address is resolved from host name of CONNECT request line.' } # These maps parse the original Host and URI from a /forcecache redirect. map $request_uri $realHost { ~/forcecacheinsecure/([^:/]+)/originalwas(/.+) $1; ~/forcecachesecure/([^:/]+)/originalwas(/.+) $1; default "DID_NOT_MATCH_HOST"; } map $request_uri $realPath { ~/forcecacheinsecure/([^:/]+)/originalwas(/.+) $2; ~/forcecachesecure/([^:/]+)/originalwas(/.+) $2; default "DID_NOT_MATCH_PATH"; } # The proxy director layer, listens on 3128 server { listen 3128; server_name _; # dont log the CONNECT proxy. #access_log /var/log/nginx/access.log debug_proxy; access_log off; proxy_connect; proxy_connect_address $interceptedHost; proxy_max_temp_file_size 0; # We need to resolve the real names of our proxied servers. #resolver 8.8.8.8 4.2.2.2 ipv6=off; # Avoid ipv6 addresses for now include /etc/nginx/resolvers.conf; # forward proxy for non-CONNECT request location / { add_header "Content-type" "text/plain" always; return 200 "docker-registry-proxy: The docker caching proxy is working!"; } location /ca.crt { alias /ca/ca.crt; } location /setup/systemd { add_header "Content-type" "text/plain" always; return 200 ' set -e if [ ! -d /etc/systemd ]; then echo "Not a systemd system" exit 1 fi mkdir -p /etc/systemd/system/docker.service.d cat << EOD > /etc/systemd/system/docker.service.d/http-proxy.conf [Service] Environment="HTTPS_PROXY=$scheme://$http_host/" EOD # Get the CA certificate from the proxy and make it a trusted root. curl $scheme://$http_host/ca.crt > /usr/share/ca-certificates/docker_registry_proxy.crt if fgrep -q "docker_registry_proxy.crt" /etc/ca-certificates.conf ; then echo "certificate refreshed" else echo "docker_registry_proxy.crt" >> /etc/ca-certificates.conf fi update-ca-certificates --fresh # Reload systemd systemctl daemon-reload # Restart dockerd systemctl restart docker.service echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" '; } # end location /setup/systemd } # end server # The caching layer server { # Listen on both 80 and 443, for all hostnames. # actually could be 443 or 444, depending on debug. this is now generated by the entrypoint. listen 80 default_server; include /etc/nginx/caching.layer.listen; server_name _; # Do some tweaked logging. access_log /var/log/nginx/access.log tweaked; # Use the generated certificates, they contain names for all the proxied registries. ssl_certificate /certs/fullchain.pem; ssl_certificate_key /certs/web.key; # We need to resolve the real names of our proxied servers. #resolver 8.8.8.8 4.2.2.2 ipv6=off; # Avoid ipv6 addresses for now include /etc/nginx/resolvers.conf; # Docker needs this. Don't ask. chunked_transfer_encoding on; # Block POST/PUT/DELETE. Don't use this proxy for pushing. if ($request_method = POST) { return 405 "POST method is not allowed"; } if ($request_method = PUT) { return 405 "PUT method is not allowed"; } if ($request_method = DELETE) { return 405 "DELETE method is not allowed"; } proxy_read_timeout 900; # Use cache locking, with a huge timeout, so that multiple Docker clients asking for the same blob at the same time # will wait for the first to finish instead of doing multiple upstream requests. proxy_cache_lock on; proxy_cache_lock_timeout 880s; # Cache all 200, 301, 302, and 307 (emitted by private registries) for 60 days. proxy_cache_valid 200 301 302 307 60d; # Some extra settings to maximize cache hits and efficiency proxy_force_ranges on; proxy_ignore_client_abort on; proxy_cache_revalidate on; # Hide/ignore headers from caching. S3 especially likes to send Expires headers in the past in some situations. proxy_hide_header Set-Cookie; proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie; # Add the authentication info, if the map matched the target domain. proxy_set_header Authorization $finalAuth; # Use SNI during the TLS handshake with the upstream. proxy_ssl_server_name on; # This comes from a include file generated by the entrypoint. include /etc/nginx/docker.verify.ssl.conf; # Block API v1. We dont know how to handle these. # Docker-client should start with v2 and fallback to v1 if something fails, for example, if authentication failed to a protected v2 resource. location /v1 { return 405 "docker-registry-proxy: docker is trying to use v1 API. Either the image does not exist upstream, or you need to configure docker-registry-proxy to authenticate against $host"; } # for the /v2/..../blobs/.... URIs, do cache, and treat redirects. location ~ ^/v2/(.*)/blobs/ { proxy_pass https://$targetHost; proxy_cache cache; add_header X-Docker-Caching-Proxy-Debug-Cache "yes:blobs"; # Handling of redirects. # Many registries (eg, quay.io, or k8s.gcr.io) emit a Location redirect # pointing to something like cloudfront, or google storage. # We hack into the response, extracting the host and URI parts, injecting them into a URL that points back to us # That gives us a chance to intercept and cache those, which are the actual multi-megabyte blobs we originally wanted to cache. # We to it twice, one for http and another for https. proxy_redirect ~^https://([^:/]+)(/.+)$ https://docker.caching.proxy.internal/forcecachesecure/$1/originalwas$2; proxy_redirect ~^http://([^:/]+)(/.+)$ http://docker.caching.proxy.internal/forcecacheinsecure/$1/originalwas$2; } # handling for the redirect case explained above, with https. # The $realHost and $realPath variables come from a map defined at the top of this file. location /forcecachesecure { proxy_pass https://$realHost$realPath; proxy_cache cache; # Change the cache key, so that we can cache signed S3 requests and such. Only host and path are considered. proxy_cache_key $proxy_host$uri; add_header X-Docker-Caching-Proxy-Debug-Cache "yes:forcecachesecure"; } # handling for the redirect case explained above, with http. # The $realHost and $realPath variables come from a map defined at the top of this file. location /forcecacheinsecure { proxy_pass http://$realHost$realPath; proxy_cache cache; # Change the cache key, so that we can cache signed S3 requests and such. Only host and path are considered. proxy_cache_key $proxy_host$uri; add_header X-Docker-Caching-Proxy-Debug-Cache "yes:forcecacheinsecure"; } # by default, dont cache anything. location / { proxy_pass https://$targetHost; proxy_cache off; add_header X-Docker-Caching-Proxy-Debug-Cache "no:default"; } } }