diff --git a/.dockerignore b/.dockerignore index 5508d37..37c4b0a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,7 @@ *.iml LICENSE README.md -docker_mirror_cache -docker_mirror_certs +registry_proxy_mirror_cache +registry_proxy_mirror_certs .github Makefile diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index f190b76..5f411a1 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -1,40 +1,51 @@ -name: Create and publish a Docker image +name: Create and publish a container image on: push: branches: ['main'] -env: - GHCR_IO_USER: ${{ github.actor }} - GHCR_IO_TOKEN: ${{ github.token }} - DOCKER_IO_USER: ${{ secrets.DOCKER_IO_USER }} - DOCKER_IO_TOKEN: ${{ secrets.DOCKER_IO_TOKEN }} - REPO_NAME: ${{ github.repository }} + +permissions: + contents: read + packages: write jobs: build-and-push-image: runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4.1.7 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3.2.0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.5.0 + + - name: Login to GHCR + uses: docker/login-action@v3.3.0 with: - submodules: recursive - - name: Docker logins - run: | - docker login -u="$GHCR_IO_USER" -p="$GHCR_IO_TOKEN" ghcr.io - docker login -u="$DOCKER_IO_USER" -p="$DOCKER_IO_TOKEN" docker.io + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} - name: Get current date id: date - run: echo "date=$(date +'%Y-%m-%d-%H-%M-%S')" >> $GITHUB_OUTPUT - - name: Build the Docker image - run: | - docker build ./ --tag ghcr.io/$REPO_NAME:${{ steps.date.outputs.date }} - - name: Docker Push - run: | - docker push ghcr.io/$REPO_NAME:${{ steps.date.outputs.date }} - docker tag ghcr.io/$REPO_NAME:${{ steps.date.outputs.date }} ghcr.io/$REPO_NAME:latest - docker push ghcr.io/$REPO_NAME:latest + run: echo "date=$(date -u +'%Y-%m-%d-%H-%M-%S')" >> $GITHUB_OUTPUT + + - name: Docker meta + id: metadata + uses: docker/metadata-action@v5.5.1 + with: + images: | + ghcr.io/${{ github.repository }} + tags: | + type=raw,value=${{ steps.date.outputs.date }} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push + uses: docker/build-push-action@v6.5.0 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} diff --git a/.github/workflows/mirror.yaml b/.github/workflows/mirror.yaml deleted file mode 100644 index 477ae44..0000000 --- a/.github/workflows/mirror.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: Mirror to Gitlab - -on: [push] - -jobs: - mirror: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - uses: yesolutions/mirror-action@v0.6.0 - with: - REMOTE: 'https://gitlab.com/${{ github.repository }}' - GIT_USERNAME: ${{ secrets.ORG_GITLAB_SYNC_UN }} - GIT_PASSWORD: ${{ secrets.ORG_GITLAB_SYNC_PW }} diff --git a/.gitignore b/.gitignore index 784dd72..8347d14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .idea *.iml -**/docker_mirror_cache -**/docker_mirror_certs +**/registry_proxy_mirror_cache +**/registry_proxy_mirror_certs diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 0569ec2..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,100 +0,0 @@ -default: - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [ "" ] - tags: - - ord1-tenant - -workflow: - rules: - - if: $CI_COMMIT_TAG - when: never - - if: '$CI_COMMIT_BRANCH != "coreweave"' - variables: - DEBUG_IMAGE: "1" - IMAGE_SUFFIX: "-debug" - - if: '$CI_COMMIT_BRANCH == "coreweave"' - variables: - DEBUG_IMAGE: "0" - IMAGE_SUFFIX: "" - -stages: - - build - - release - -build: - stage: build - variables: - REF_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG - FIXED_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA - DOCKERFILE: ${CI_PROJECT_DIR}/Dockerfile - REGPROXY_URI: ${REGPROXY_URI} - before_script: - - export HTTP_PROXY=${REGPROXY_URI} - - export HTTPS_PROXY=${REGPROXY_URI} - - mkdir -p /etc/ssl/certs/ && cat /etc/gitlab-runner/certs/proxy-ca.crt >> /etc/ssl/certs/ca-certificates.crt - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - script: |- - /kaniko/executor \ - $KANIKO_ADDTL_ARGS \ - --context ${CI_PROJECT_DIR} \ - --build-arg DEBUG_IMAGE=${DEBUG_IMAGE} \ - --build-arg IMAGE_SUFFIX=${IMAGE_SUFFIX} \ - --dockerfile $DOCKERFILE \ - --destination $REF_IMAGE \ - --destination $FIXED_IMAGE - only: - changes: - - Dockerfile - - entrypoint.sh - - LICENSE - - liveliness.sh - - nginx.conf - - nginx.manifest.common.conf - - nginx.manifest.stale.conf - - create_ca_cert.sh - - .gitlab-ci.yml - -build:release: - stage: build - image: node:lts-alpine3.15 - only: - refs: - - coreweave - artifacts: - paths: - - artifacts.env - expire_in: 1 day - before_script: - - apk --no-cache add git - script: - - npm install - - npx semantic-release - -release: - stage: release - only: - refs: - - coreweave - dependencies: - - build:release - variables: - DOCKERFILE: $CI_PROJECT_DIR/Dockerfile - LATEST_IMAGE: $CI_REGISTRY_IMAGE:latest - REGPROXY_URI: ${REGPROXY_URI} - before_script: - - export HTTP_PROXY=${REGPROXY_URI} - - export HTTPS_PROXY=${REGPROXY_URI} - - mkdir -p /etc/ssl/certs/ && cat /etc/gitlab-runner/certs/proxy-ca.crt >> /etc/ssl/certs/ca-certificates.crt - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - - export $(cat artifacts.env | xargs) - - export RELEASE_IMAGE=$CI_REGISTRY_IMAGE:$BUILD_VERSION - script: |- - /kaniko/executor \ - $KANIKO_ADDTL_ARGS \ - --context . \ - --dockerfile $DOCKERFILE \ - --destination $RELEASE_IMAGE \ - --destination $LATEST_IMAGE diff --git a/.releaserc.yaml b/.releaserc.yaml index 8606147..e93652e 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -5,7 +5,7 @@ tagFormat: v${version} plugins: - "@semantic-release/commit-analyzer" - "@semantic-release/exec" -repositoryUrl: "git@github.com:coreweave/docker-registry-proxy.git" +repositoryUrl: "git@github.com:gmarcy/container-registry-proxy.git" analyzeCommits: - path: "@semantic-release/commit-analyzer" releaseRules: diff --git a/Docker-Desktop-Windows.md b/Docker-Desktop-Windows.md deleted file mode 100644 index f733bf3..0000000 --- a/Docker-Desktop-Windows.md +++ /dev/null @@ -1,66 +0,0 @@ -# Configure Docker Desktop on Windows to use the proxy and trust its certificate - -1. Let's say you set up the proxy on host `192.168.66.72`. Get the certificate using a browser (go to ) and save it as a file (e.g., to `d:\ca.crt`) - -1. Add the certificate to Windows: - - 1. Double click the certificate - 1. Chose to _Install certificate..._, then click _Next_ - 1. Chose _Current user_, then click _Next_ - 1. Select option _Place all certificates in the following store_, click _browse_, and select _Trusted Root Certification Authorities_ - 1. Proceed with Ok and confirm to install the certificate - - If you are not using the WSL2 backend for Docker, then restart Docker Desktop and skip the next step. - -1. If you are using WSL2 for Docker, then you need to add the certificate to WSL too: - - 1. Open a terminal - - 1. Check the name of the WSL distribution: - - ``` - PS C:\> wsl --list - Windows Subsystem for Linux Distributions: - docker-desktop (Default) - docker-desktop-data - ``` - - The distribution we are looking for is _docker-desktop_. If you installed another distribution, such as Ubuntu, and configured Docker to use that, and proceed with that distribution instead. - - 1. Get a shell into WSL - - ``` - PS C:\> wsl --distribution docker-desktop - XXXYYYZZZ:/tmp/docker-desktop-root/mnt/host/c# - ``` - - 1. Copy the certificate into WSL and import it - - Note: The directory and the command below are for the _docker-desktop_ WSL distribution. On other systems you might need to tweak the commands a little, but they seem to be the same for [Ubuntu](https://www.pmichaels.net/2020/12/29/add-certificate-into-wsl/) and [Debian](https://github.com/microsoft/WSL/issues/3161#issue-320777324) as well. - - ``` - XXXYYYZZZ:/tmp/docker-desktop-root/mnt/host/c# cp /mnt/host/d/ca.crt /usr/local/share/ca-certificates/ - XXXYYYZZZ:/tmp/docker-desktop-root/mnt/host/c# update-ca-certificates - WARNING: ca-certificates.crt does not contain exactly one certificate or CRL: skipping - ``` - - Don't mind the warning, the operation still succeeded. - - 1. We are done with WSL, you can `exit` this shell - -1. Configure the proxy in Docker Desktop: - - 1. Open Docker Desktop settings - 1. Go to _Resources/Proxies_ - 1. Enable the proxy and set `http://192.168.66.72:3128` as both the HTTP and HTTPS URL. - -1. Done. Verify that pulling works: - - ``` - # execute this in a Windows shell, not in WSL - docker pull hello-world - ``` - - You can check the logs of the proxy to confirm that it was used. - - If pulling does not work and complains about not trusting the certificate then Docker and/or the WSL distribution might need a restart. You might try restarting Docker, or you can restart Windows too to force WSL to restart. diff --git a/Docker-for-Mac.md b/Docker-for-Mac.md deleted file mode 100644 index 6562cab..0000000 --- a/Docker-for-Mac.md +++ /dev/null @@ -1,74 +0,0 @@ -# Attention: don't use Docker's own GUI to set the proxy! - -- See https://github.com/docker/for-mac/issues/2467 -- In `Docker > Preferences`, in `Resources > Proxies`, make sure you're NOT using manual proxies -- Use the hack below to set the environment var directly in LinuxKit -- The issue is that setting it in the GUI affects containers too (!!!), and we don't want that in this scenario -- If you actually need an upstream proxy (for company proxy etc) this will NOT work. - -# Using a Docker Desktop for Mac as a client for the proxy - -First, know this is a MiTM, and could break with new Docker Desktop for Mac releases or during resets/reinstalls/upgrades. - -These instructions tested on Mac OS Catalina, and: -- Docker Desktop for Mac `2.4.2.0` (Edge) (which provides Docker `20.10.0-beta1`) -- Docker Desktop for Mac `2.5.0.0` (Stable) (which provides Docker `19.03`) - -This assumes you have `docker-registry-proxy` running _somewhere else_, eg, on a different machine on your local network. - -See the main [README.md](README.md) for instructions. (If you're trying to run both proxy and client on the same machine, see below). - -We'll inject the CA certificates and the HTTPS_PROXY env into the Docker install inside the HyperKit VM running LinuxKit that is used by Docker Desktop for Mac. - -To do that, we use a privileged container. `justincormack/nsenter1` does the job nicely. - -First things first: - -### 1) Factory Reset Docker Desktop for Mac... -... or make sure it's pristine (just installed). - -- Go into Troubleshoot > "Reset to Factory defaults" -- it will take a while to reset/restart everything and require your password. - -### 2) Inject config into Docker's VM - -For these examples I will assume it is successfully running on `http://192.168.1.2:3128/` -- -change the `export DRP_PROXY` as appropriate. Do not include slashes. - -Run these commands in your Mac terminal. - -```bash -set -e -export DRP_PROXY="192.168.66.100:3129" # Format IP:port, change this -wget -O - "http://${DRP_PROXY}/" # Make sure you can reach the proxy -# Inject the CA certificate -docker run -it --privileged --pid=host justincormack/nsenter1 \ - /bin/bash -c "wget -O - http://$DRP_PROXY/ca.crt \ - | tee -a /containers/services/docker/lower/etc/ssl/certs/ca-certificates.crt" - -# Preserve original config. -docker run -it --privileged --pid=host justincormack/nsenter1 /bin/bash -c "cp /containers/services/docker/config.json /containers/services/docker/config.json.orig" - -# Inject the HTTPS_PROXY enviroment variable. I dare you find a better way. -docker run -it --privileged --pid=host justincormack/nsenter1 /bin/bash -c "sed -ibeforedockerproxy -e 's/\"PATH=/\"HTTPS_PROXY=http:\/\/$DRP_PROXY\/\",\"PATH=/' /containers/services/docker/config.json" -``` - -### 3) Restart, test. - -- Restart Docker. (Quit & Open again, or just go into Preferences and give it more RAM, then Restart.) -- Try a `docker pull` now. It should be using the proxy (watch the logs on the proxy server). -- Test that no crazy proxy has been set: `docker run -it curlimages/curl:latest http://ifconfig.me` and `docker run -it curlimages/curl:latest https://ifconfig.me` both work. -- Important: **push**es done with this configured will either not work, or use the auth you configured on the proxy, if any. Beware, and report back. - - -# Using Docker Desktop for Mac to both host the proxy server and use it as a client - -@TODO: This has a bunch of chicken-and-egg issues. - -You need to pre-pull the proxy itself and `justincormack/nsenter1`. - -Follow the instructions above, but pre-pull after the Factory Reset. - -Do NOT use 127.0.0.1, instead use your machine's local LAN IP address. - -Make sure to bring the proxy up after applying/restarting the Docker Engine. diff --git a/Dockerfile b/Dockerfile index a6aa9b5..84f09e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ARG BASE_IMAGE_SUFFIX="${IMAGE_SUFFIX}" FROM ${BASE_IMAGE}${BASE_IMAGE_SUFFIX} # Link image to original repository on GitHub -LABEL org.opencontainers.image.source https://github.com/rpardini/docker-registry-proxy +LABEL org.opencontainers.image.source=https://github.com/gmarcy/container-registry-proxy # apk packages that will be present in the final image both debug and release RUN apk add --no-cache --update bash ca-certificates-bundle coreutils openssl @@ -34,13 +34,13 @@ ENV LANG=en_US.UTF-8 RUN [[ "a$DO_DEBUG_BUILD" == "a1" ]] && { mitmproxy --version && mitmweb --version ; } || { echo "Debug build disabled."; } # Create the cache directory and CA directory -RUN mkdir -p /docker_mirror_cache /ca +RUN mkdir -p /registry_proxy__mirror_cache /ca -# Expose it as a volume, so cache can be kept external to the Docker image -VOLUME /docker_mirror_cache +# Expose it as a volume, so cache can be kept external to the container image +VOLUME /registry_proxy_mirror_cache # Expose /ca as a volume. Users are supposed to volume mount this, as to preserve it across restarts. -# Actually, its required; if not, then docker clients will reject the CA certificate when the proxy is run the second time +# Actually, its required; if not, then clients will reject the CA certificate when the proxy is run the second time VOLUME /ca # Add our configuration @@ -61,22 +61,13 @@ RUN chmod +x /liveliness.sh # Clients should only use 3128, not anything else. EXPOSE 3128 -# In debug mode, 8081 exposes the mitmweb interface (for incoming requests from Docker clients) -EXPOSE 8081 -# In debug-hub mode, 8082 exposes the mitmweb interface (for outgoing requests to DockerHub) -EXPOSE 8082 - ## Default envs. -# A space delimited list of registries we should proxy and cache; this is in addition to the central DockerHub. +# A space delimited list of registries we should proxy and cache. ENV REGISTRIES="k8s.gcr.io gcr.io quay.io" # A space delimited list of registry:user:password to inject authentication for ENV AUTH_REGISTRIES="some.authenticated.registry:oneuser:onepassword another.registry:user:password" # Should we verify upstream's certificates? Default to true. ENV VERIFY_SSL="true" -# Enable debugging mode; this inserts mitmproxy/mitmweb between the CONNECT proxy and the caching layer -ENV DEBUG="false" -# Enable debugging mode; this inserts mitmproxy/mitmweb between the caching layer and DockerHub's registry -ENV DEBUG_HUB="false" # Enable nginx debugging mode; this uses nginx-debug binary and enabled debug logging, which is VERY verbose so separate setting ENV DEBUG_NGINX="false" # Enable slow caching tier; this allows caching in a secondary cache path on e.g a larger slower disk; for known URIs defined in SLOW_TIER_URIS @@ -86,7 +77,7 @@ ENV WORKER_PROCESSES="auto" # Manifest caching tiers. Disabled by default, to mimick 0.4/0.5 behaviour. # Setting it to true enables the processing of the ENVs below. -# Once enabled, it is valid for all registries, not only DockerHub. +# Once enabled, it is valid for all registries. # The envs *_REGEX represent a regex fragment, check entrypoint.sh to understand how they're used (nginx ~ location, PCRE syntax). ENV ENABLE_MANIFEST_CACHE="false" @@ -144,5 +135,5 @@ ENV PROXY_CONNECT_READ_TIMEOUT="60s" ENV PROXY_CONNECT_CONNECT_TIMEOUT="60s" ENV PROXY_CONNECT_SEND_TIMEOUT="60s" -# Did you want a shell? Sorry, the entrypoint never returns, because it runs nginx itself. Use 'docker exec' if you need to mess around internally. +# Did you want a shell? Sorry, the entrypoint never returns, because it runs nginx itself. Use 'podman exec' if you need to mess around internally. ENTRYPOINT ["/entrypoint.sh"] diff --git a/README.md b/README.md index 25943c7..3a714b0 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,12 @@ -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/rpardini/docker-registry-proxy/master-latest?label=%3Alatest%20from%20master) -![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/rpardini/docker-registry-proxy?label=last%20tagged%20release) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/rpardini/docker-registry-proxy/tags?label=last%20tagged%20release) -![Docker Image Size (latest semver)](https://img.shields.io/docker/image-size/rpardini/docker-registry-proxy?sort=semver) -![Docker Pulls](https://img.shields.io/docker/pulls/rpardini/docker-registry-proxy) ## TL,DR -A caching proxy for Docker; allows centralised management of (multiple) registries and their authentication; caches images from *any* registry. +A caching proxy for container images; allows centralised management of (multiple) registries and their authentication; caches images from *any* registry. Caches the potentially huge blob/layer requests (for bandwidth/time savings), and optionally caches manifest requests ("pulls") to avoid rate-limiting. -### NEW: avoiding DockerHub Pull Rate Limits with Caching +### Avoiding Container Registry Pull Rate Limits with Caching -Starting November 2nd, 2020, DockerHub will -[supposedly](https://www.docker.com/blog/docker-hub-image-retention-policy-delayed-and-subscription-updates/) -[start](https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/) -[rate-limiting pulls](https://docs.docker.com/docker-hub/download-rate-limit/), -also known as the _Docker Apocalypse_. -The main symptom is `Error response from daemon: toomanyrequests: Too Many Requests. Please see https://docs.docker.com/docker-hub/download-rate-limit/` during pulls. -Many unknowing Kubernetes clusters will hit the limit, and struggle to configure `imagePullSecrets` and `imagePullPolicy`. - -Since version `0.6.0`, this proxy can be configured with the env var `ENABLE_MANIFEST_CACHE=true` which provides -configurable caching of the manifest requests that DockerHub throttles. You can then fine-tune other parameters to your needs. +This proxy can be configured with the env var `ENABLE_MANIFEST_CACHE=true` which provides +configurable caching of the manifest requests that may be throttled. You can then fine-tune other parameters to your needs. Together with the possibility to centrally inject authentication (since 0.3x), this is probably one of the best ways to bring relief to your distressed cluster, while at the same time saving lots of bandwidth and time. Note: enabling manifest caching, in its default config, effectively makes some tags **immutable**. Use with care. The configuration ENVs are explained in the [Dockerfile](./Dockerfile), relevant parts included below. @@ -27,7 +14,7 @@ Note: enabling manifest caching, in its default config, effectively makes some t ```dockerfile # Manifest caching tiers. Disabled by default, to mimick 0.4/0.5 behaviour. # Setting it to true enables the processing of the ENVs below. -# Once enabled, it is valid for all registries, not only DockerHub. +# Once enabled, it is valid for all registries. # The envs *_REGEX represent a regex fragment, check entrypoint.sh to understand how they're used (nginx ~ location, PCRE syntax). ENV ENABLE_MANIFEST_CACHE="false" @@ -51,38 +38,29 @@ ENV MANIFEST_CACHE_DEFAULT_TIME="1h" ## What? -Essentially, it's a [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack): an intercepting proxy based on `nginx`, to which all docker traffic is directed using the `HTTPS_PROXY` mechanism and injected CA root certificates. +Essentially, it's a [man in the middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack): an intercepting proxy based on `nginx`, to which all container image traffic is directed using the `HTTPS_PROXY` mechanism and injected CA root certificates. -The main feature is Docker layer/image caching, including layers served from S3, Google Storage, etc. +The main feature is container image layer/image caching, including layers served from S3, Google Storage, etc. -As a bonus it allows for centralized management of Docker registry credentials, which can in itself be the main feature, eg in Kubernetes environments. +As a bonus it allows for centralized management of container registry credentials, which can in itself be the main feature, eg in Kubernetes environments. -You configure the Docker clients (_err... Kubernetes Nodes?_) once, and then all configuration is done on the proxy -- +You configure the clients (_err... Kubernetes Nodes?_) once, and then all configuration is done on the proxy -- for this to work it requires inserting a root CA certificate into system trusted root certs. -## master/:latest is unstable/beta - -- `:latest` and `:latest-debug` Docker tag is unstable, built from master, and amd64-only -- Production/stable is `0.6.2`, see [0.6.2 tag on Github](https://github.com/rpardini/docker-registry-proxy/tree/0.6.2) - this image is multi-arch amd64/arm64 -- The previous version is `0.5.0`, without any manifest caching, see [0.5.0 tag on Github](https://github.com/rpardini/docker-registry-proxy/tree/0.5.0) - this image is multi-arch amd64/arm64 - -## Also hosted on GitHub Container Registry (ghcr.io) +## Hosted on GitHub Container Registry (ghcr.io) -- DockerHub image is at `rpardini/docker-registry-proxy:` -- GitHub image is at `ghcr.io/rpardini/docker-registry-proxy:` -- Since 0.5.x, they both carry the same images -- This can be useful if you're already hitting DockerHub's rate limits and can't pull the proxy from DockerHub +- GitHub image is at `ghcr.io/gmarcy/container-registry-proxy:` ## Usage (running the Proxy server) -- Run the proxy on a host close (network-wise: high bandwidth, same-VPC, etc) to the Docker clients +- Run the proxy on a host close (network-wise: high bandwidth, same-VPC, etc) to the clients - Expose port 3128 to the network -- Map volume `/docker_mirror_cache` for up to `CACHE_MAX_SIZE` (32gb by default) of cached images across all cached registries +- Map volume `/registry_proxy_mirror_cache` for up to `CACHE_MAX_SIZE` (32gb by default) of cached images across all cached registries - Map volume `/ca`, the proxy will store the CA certificate here across restarts. **Important** this is security sensitive. -- Env `ALLOW_PUSH` : This bypasses the proxy when pushing, default to false - if kept to false, pushing will not work. For more info see this [commit](https://github.com/rpardini/docker-registry-proxy/commit/536f0fc8a078d03755f1ae8edc19a86fc4b37fcf). -- Env `CACHE_MAX_SIZE` (default `32g`): set the max size to be used for caching local Docker image layers. Use [Nginx sizes](http://nginx.org/en/docs/syntax.html). +- Env `ALLOW_PUSH` : This bypasses the proxy when pushing, default to false - if kept to false, pushing will not work. +- Env `CACHE_MAX_SIZE` (default `32g`): set the max size to be used for caching local container image layers. Use [Nginx sizes](http://nginx.org/en/docs/syntax.html). - Env `ENABLE_MANIFEST_CACHE`, see the section on pull rate limiting. -- Env `REGISTRIES`: space separated list of registries to cache; no need to include DockerHub, its already done internally. +- Env `REGISTRIES`: space separated list of registries to cache. - Env `AUTH_REGISTRIES`: space separated list of `hostname:username:password` authentication info. - `hostname`s listed here should be listed in the REGISTRIES environment as well, so they can be intercepted. - Env `AUTH_REGISTRIES_DELIMITER` to change the separator between authentication info. By default, a space: "` `". If you use keys that contain spaces (as with Google Cloud Registry), you should update this variable, e.g. setting it to `AUTH_REGISTRIES_DELIMITER=";;;"`. In that case, `AUTH_REGISTRIES` could contain something like `registry1.com:user1:pass1;;;registry2.com:user2:pass2`. @@ -106,34 +84,30 @@ ENV PROXY_REQUEST_BUFFERING="true" ### Simple (no auth, all cache) ```bash -docker run --rm --name docker_registry_proxy -it \ +podman run --rm --name container_registry_proxy -it \ -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ - rpardini/docker-registry-proxy:0.6.2 + -v $(pwd)/registry_proxy_mirror_cache:/registry_proxy_mirror_cache \ + -v $(pwd)/registry_proxy_mirror_certs:/ca \ + gmarcy/container-registry-proxy:latest ``` -### DockerHub auth - -For Docker Hub authentication: -- `hostname` should be `auth.docker.io` -- `username` should NOT be an email, use the regular username +### Container Registry auth ```bash -docker run --rm --name docker_registry_proxy -it \ +podman run --rm --name container_registry_proxy -it \ -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ + -v $(pwd)/registry_proxy_mirror_cache:/registry_proxy_mirror_cache \ + -v $(pwd)/registry_proxy_mirror_certs:/ca \ -e REGISTRIES="k8s.gcr.io gcr.io quay.io your.own.registry another.public.registry" \ - -e AUTH_REGISTRIES="auth.docker.io:dockerhub_username:dockerhub_password your.own.registry:username:password" \ - rpardini/docker-registry-proxy:0.6.2 + -e AUTH_REGISTRIES="quay.io:quay_username:quay_password your.own.registry:username:password" \ + gmarcy/container-registry-proxy:latest ``` ### Simple registries auth (HTTP Basic auth) For regular registry auth (HTTP Basic), the `hostname` should be the registry itself... unless your registry uses a different auth server. -See the example above for DockerHub, adapt the `your.own.registry` parts (in both ENVs). +See the example above, adapt the `your.own.registry` parts (in both ENVs). This should work for quay.io also, but I have no way to test. @@ -141,20 +115,18 @@ This should work for quay.io also, but I have no way to test. GitLab may use a different/separate domain to handle the authentication procedure. -Just like DockerHub uses `auth.docker.io`, GitLab uses its primary (git) domain for the authentication. - If you run GitLab on `git.example.com` and its registry on `reg.example.com`, you need to include both in `REGISTRIES` and use the primary domain for `AUTH_REGISTRIES`. For GitLab.com itself the authentication domain should be `gitlab.com`. ```bash -docker run --rm --name docker_registry_proxy -it \ +podman run --rm --name container_registry_proxy -it \ -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ + -v $(pwd)/registry_proxy_mirror_cache:/registry_proxy_mirror_cache \ + -v $(pwd)/registry_proxy_mirror_certs:/ca \ -e REGISTRIES="reg.example.com git.example.com" \ -e AUTH_REGISTRIES="git.example.com:USER:PASSWORD" \ - rpardini/docker-registry-proxy:0.6.2 + gmarcy/container-registry-proxy:latest ``` ### Google Container Registry (GCR) auth @@ -169,32 +141,32 @@ To be able to use GCR you should set `AUTH_REGISTRIES_DELIMITER` to something di Example with GCR using credentials from a service account from a key file `servicekey.json`: ```bash -docker run --rm --name docker_registry_proxy -it \ +podman run --rm --name container_registry_proxy -it \ -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ + -v $(pwd)/registry_proxy_mirror_cache:/registry_proxy_mirror_cache \ + -v $(pwd)/registry_proxy_mirror_certs:/ca \ -e REGISTRIES="k8s.gcr.io gcr.io quay.io your.own.registry another.public.registry" \ -e AUTH_REGISTRIES_DELIMITER=";;;" \ -e AUTH_REGISTRY_DELIMITER=":::" \ - -e AUTH_REGISTRIES="gcr.io:::_json_key:::$(cat servicekey.json);;;auth.docker.io:::dockerhub_username:::dockerhub_password" \ - rpardini/docker-registry-proxy:0.6.2 + -e AUTH_REGISTRIES="gcr.io:::_json_key:::$(cat servicekey.json);;;quay.io:::quay_username:::quay_password" \ + gmarcy/container-registry-proxy:latest ``` ### Kind Cluster -[Kind](https://github.com/kubernetes-sigs/kind/) is a tool for running local Kubernetes clusters using Docker container “nodes”. +[Kind](https://github.com/kubernetes-sigs/kind/) is a tool for running local Kubernetes clusters using container “nodes”. -Because cluster nodes are Docker containers, docker-registry-proxy needs to be in the same docker network. +Because cluster nodes are containers, container-registry-proxy needs to be in the same network. -Example joining the _kind_ docker network and using hostname _docker-registry-proxy_ as hostname : +Example joining the _kind_ network and using hostname _container-registry-proxy_ as hostname : ```bash -docker run --rm --name docker_registry_proxy -it \ - --net kind --hostname docker-registry-proxy \ +podman run --rm --name container_registry_proxy -it \ + --net kind --hostname container-registry-proxy \ -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ - rpardini/docker-registry-proxy:0.6.2 + -v $(pwd)/registry_proxy_mirror_cache:/registry_proxy_mirror_cache \ + -v $(pwd)/registry_proxy_mirror_certs:/ca \ + gmarcy/container-registry-proxy:latest ``` Now deploy your Kind cluster and then automatically configure the nodes with the following script : @@ -202,85 +174,34 @@ Now deploy your Kind cluster and then automatically configure the nodes with the ```bash #!/bin/sh KIND_NAME=${1-kind} -SETUP_URL=http://docker-registry-proxy:3128/setup/systemd +SETUP_URL=http://container-registry-proxy:3128/setup/systemd pids="" for NODE in $(kind get nodes --name "$KIND_NAME"); do - docker exec "$NODE" sh -c "\ + podman exec "$NODE" sh -c "\ curl $SETUP_URL \ - | sed s/docker\.service/containerd\.service/g \ + | sed s/podman\.service/containerd\.service/g \ | sed '/Environment/ s/$/ \"NO_PROXY=127.0.0.0\/8,10.0.0.0\/8,172.16.0.0\/12,192.168.0.0\/16\"/' \ | bash" & pids="$pids $!" # Configure every node in background done wait $pids # Wait for all configurations to end ``` -### K3D Cluster - -[K3d](https://k3d.io/) is similar to Kind but is based on k3s. In order to run with its registry you need to setup settings like shown below. - -```sh -# docker-registry-proxy -docker run -d --name registry-proxy --restart=always \ --v /tmp/registry-proxy/mirror_cache:/docker_mirror_cache \ --v /tmp/registry-proxy/certs:/ca \ -rpardini/docker-registry-proxy:0.6.4 - -export PROXY_HOST=registry-proxy -export PROXY_PORT=3128 -export NOPROXY_LIST="localhost,127.0.0.1,0.0.0.0,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,.local,.svc" - -cat < /etc/k3d-proxy-config.yaml -apiVersion: k3d.io/v1alpha3 -kind: Simple -name: mycluster -servers: 1 -agents: 0 -options: - k3d: - wait: true - timeout: "60s" - kubeconfig: - updateDefaultKubeconfig: true - switchCurrentContext: true -env: - - envVar: HTTP_PROXY=http://$PROXY_HOST:$PROXY_PORT - nodeFilters: - - all - - envVar: HTTPS_PROXY=http://$PROXY_HOST:$PROXY_PORT - nodeFilters: - - all - - envVar: NO_PROXY='$NOPROXY_LIST' - nodeFilters: - - all -volumes: - - volume: $REGISTRY_DIR/docker_mirror_certs/ca.crt:/etc/ssl/certs/registry-proxy-ca.pem - nodeFilters: - - all -EOF - -k3d cluster create --config /etc/k3d-proxy-config.yaml -``` - -## Configuring the Docker clients using Docker Desktop for Mac - -Separate instructions for Mac clients available in [this dedicated Doc Desktop for Mac document](Docker-for-Mac.md). - -## Configuring the Docker clients / Kubernetes nodes / Linux clients +## Configuring the clients / Kubernetes nodes / Linux clients Let's say you setup the proxy on host `192.168.66.72`, you can then `curl http://192.168.66.72:3128/ca.crt` and get the proxy CA certificate. -On each Docker host that is to use the cache: +On each host that is to use the cache: -- [Configure Docker proxy](https://docs.docker.com/config/daemon/systemd/#httphttps-proxy) pointing to the caching server +- [Configure registry proxy](https://docs.docker.com/config/daemon/systemd/#httphttps-proxy) pointing to the caching server - Add the caching server CA certificate to the list of system trusted roots. - Restart `dockerd` Do it all at once, tested on Ubuntu Xenial, Bionic, and Focal, all systemd based: ```bash -# Add environment vars pointing Docker to use the proxy -mkdir -p /etc/systemd/system/docker.service.d -cat << EOD > /etc/systemd/system/docker.service.d/http-proxy.conf +# Add environment vars pointing Podman to use the proxy +mkdir -p /usr/lib/systemd/system/docker.service.d +cat << EOD > /usr/lib/systemd/system/podman.service.d/http-proxy.conf [Service] Environment="HTTP_PROXY=http://192.168.66.72:3128/" Environment="HTTPS_PROXY=http://192.168.66.72:3128/" @@ -288,14 +209,14 @@ EOD ### UBUNTU # Get the CA certificate from the proxy and make it a trusted root. -curl http://192.168.66.72:3128/ca.crt > /usr/share/ca-certificates/docker_registry_proxy.crt -echo "docker_registry_proxy.crt" >> /etc/ca-certificates.conf +curl http://192.168.66.72:3128/ca.crt > /usr/share/ca-certificates/container_registry_proxy.crt +echo "container_registry_proxy.crt" >> /etc/ca-certificates.conf update-ca-certificates --fresh ### ### CENTOS # Get the CA certificate from the proxy and make it a trusted root. -curl http://192.168.66.72:3128/ca.crt > /etc/pki/ca-trust/source/anchors/docker_registry_proxy.crt +curl http://192.168.66.72:3128/ca.crt > /etc/pki/ca-trust/source/anchors/container_registry_proxy.crt update-ca-trust ### @@ -303,38 +224,20 @@ update-ca-trust systemctl daemon-reload # Restart dockerd -systemctl restart docker.service +systemctl restart podman.service ``` ## Testing -Clear `dockerd` of everything not currently running: `docker system prune -a -f` *beware* +Clear `podman.service` of everything not currently running: `podman system prune -a -f` *beware* -Then do, for example, `docker pull k8s.gcr.io/kube-proxy-amd64:v1.10.4` and watch the logs on the caching proxy, it should list a lot of MISSes. +Then do, for example, `podman pull k8s.gcr.io/kube-proxy-amd64:v1.10.4` and watch the logs on the caching proxy, it should list a lot of MISSes. Then, clean again, and pull again. You should see HITs! Success. -Do the same for `docker pull ubuntu` and rejoice. - -Test your own registry caching and authentication the same way; you don't need `docker login`, or `.docker/config.json` anymore. - -## Developing/Debugging - -Since `0.4` there is a separate `-debug` version of the image, which includes `nginx-debug`, and (since 0.5.x) has a `mitmproxy` (actually `mitmweb`) inserted after the CONNECT proxy but before the caching logic, and a second `mitmweb` between the caching layer and DockerHub. -This allows very in-depth debugging. Use sparingly, and definitely not in production. +Do the same for `podman pull ubuntu` and rejoice. -```bash -docker run --rm --name docker_registry_proxy -it - -e DEBUG_NGINX=true -e DEBUG=true -e DEBUG_HUB=true -p 0.0.0.0:8081:8081 -p 0.0.0.0:8082:8082 \ - -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ - rpardini/docker-registry-proxy:0.6.2-debug -``` - -- `DEBUG=true` enables the mitmweb proxy between Docker clients and the caching layer, accessible on port 8081 -- `DEBUG_HUB=true` enables the mitmweb proxy between the caching layer and DockerHub, accessible on port 8082 (since 0.5.x) -- `DEBUG_NGINX=true` enables nginx-debug and debug logging, which probably is too much. Seriously. +Test your own registry caching and authentication the same way; you don't need `podman login`, or `.docker/config.json` anymore. ## Gotchas @@ -342,30 +245,3 @@ docker run --rm --name docker_registry_proxy -it - Repeat, **this will make your private images very public if you're not careful**. - ~~**Currently you cannot push images while using the proxy** which is a shame. PRs welcome.~~ **SEE `ALLOW_PUSH` ENV FROM USAGE SECTION.** - Setting this on Linux is relatively easy. - - On Mac follow the instructions [here](Docker-for-Mac.md). - - On Windows follow the instructions [here](Docker-Desktop-Windows.md). - -### Why not use Docker's own registry, which has a mirror feature? - -Yes, Docker offers [Registry as a pull through cache](https://docs.docker.com/registry/recipes/mirror/), *unfortunately* -it only covers the DockerHub case. It won't cache images from `quay.io`, `k8s.gcr.io`, `gcr.io`, or any such, including any private registries. - -That means that your shiny new Kubernetes cluster is now a bandwidth hog, since every image will be pulled from the -Internet on every Node it runs on, with no reuse. - -This is due to the way the Docker "client" implements `--registry-mirror`, it only ever contacts mirrors for images -with no repository reference (eg, from DockerHub). -When a repository is specified `dockerd` goes directly there, via HTTPS (and also via HTTP if included in a -`--insecure-registry` list), thus completely ignoring the configured mirror. - -### Docker itself should provide this. - -Yeah. Docker Inc should do it. So should NPM, Inc. Wonder why they don't. 😼 - -### TODO: - -- [x] Basic Docker-for-Mac set-up instructions -- [x] Basic Docker-for-Windows set-up instructions. -- [ ] Test and make auth work with quay.io, unfortunately I don't have access to it (_hint, hint, quay_) -- [x] Hide the mitmproxy building code under a Docker build ARG. -- [ ] "Developer Office" proxy scenario, where many developers on a fast LAN share a proxy for bandwidth and speed savings (already works for pulls, but messes up pushes, which developers tend to use a lot) diff --git a/create_ca_cert.sh b/create_ca_cert.sh index b7c9352..b177278 100644 --- a/create_ca_cert.sh +++ b/create_ca_cert.sh @@ -8,7 +8,7 @@ logInfo() { echo "INFO: $@" } -PROJ_NAME=DockerMirrorBox +PROJ_NAME=RegistryProxyMirrorBox logInfo "Will create certificate with names $ALLDOMAINS" CADATE=$(date "+%Y.%m.%d %H:%M") diff --git a/docs/compose/docker-compose.yml b/docs/compose/docker-compose.yml index 012da76..53dd160 100644 --- a/docs/compose/docker-compose.yml +++ b/docs/compose/docker-compose.yml @@ -1,8 +1,8 @@ version: '3.7' services: - docker_registry_proxy: - image: rpardini/docker-registry-proxy:0.6.1 # Check and make sure this is the last released version + container_registry_proxy: + image: gmarcy/container-registry-proxy:latest env_file: # This contains REGISTRIES and AUTH_REGISTRIES - ./secrets.env environment: @@ -10,7 +10,7 @@ services: - ENABLE_MANIFEST_CACHE=true volumes: # Format: :; adapt to your needs - - ./docker_mirror_cache:/docker_mirror_cache # This will be up to CACHE_MAX_SIZE big - - ./docker_mirror_certs:/ca + - ./registry_proxy_mirror_cache:/registry_proxy_mirror_cache # This will be up to CACHE_MAX_SIZE big + - ./registry_proxy_mirror_certs:/ca ports: - 0.0.0.0:3128:3128 # 0.0.0.0 binds to all interfaces diff --git a/docs/compose/secrets.env b/docs/compose/secrets.env index b996632..545dd50 100644 --- a/docs/compose/secrets.env +++ b/docs/compose/secrets.env @@ -1,3 +1,3 @@ -# DockerHub authentication -REGISTRIES="k8s.gcr.io gcr.io quay.io" # There is no need to specify auth.docker.io, it's built-in -AUTH_REGISTRIES="auth.docker.io:your_dockerhub_username:your_dockerhub_password" +# Container Registry authentication +REGISTRIES="k8s.gcr.io gcr.io quay.io" +AUTH_REGISTRIES="quay.io:your_quay_username:your_quay_password" diff --git a/docs/kops/README.md b/docs/kops/README.md deleted file mode 100644 index 883674d..0000000 --- a/docs/kops/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# How to use docker-registry-proxy with kops - -## Install docker-registry-proxy - -For running docker-registry-proxy with kops you will need to run it outside the cluster you want to configure, you can either use and EC2 instance and run: - -```bash -docker run --rm --name docker_registry_proxy -it \ - -p 0.0.0.0:3128:3128 -e ENABLE_MANIFEST_CACHE=true \ - -v $(pwd)/docker_mirror_cache:/docker_mirror_cache \ - -v $(pwd)/docker_mirror_certs:/ca \ - rpardini/docker-registry-proxy:0.6.0 -``` - -or you can run it from another cluster, maybe a management/observability one with provided yaml, in this case, you will need to change the following lines: - -``` - annotations: - external-dns.alpha.kubernetes.io/hostname: docker-registry-proxy. - service.beta.kubernetes.io/aws-load-balancer-internal: "true" -``` - -with the correct domain name, so then you can reference the proxy as `http://docker-registry-proxy.:3128` - -## Test the connection to the proxy - -A simple curl should return: - -``` -❯ curl docker-registry-proxy.:3128 -docker-registry-proxy: The docker caching proxy is working!% -``` - -## Configure kops to use the proxy - -Kops has the option to configure a cluster wide proxy, as explained [here](https://github.com/kubernetes/kops/blob/master/docs/http_proxy.md) but this wont work, as nodeup will fail to download the images, what you need is to use `additionalUserData`, which is part of the instance groups configuration. - -So consider a node configuration like this one: - -``` -apiVersion: kops.k8s.io/v1alpha2 -kind: InstanceGroup -metadata: - labels: - kops.k8s.io/cluster: spot.k8s.local - name: spotgroup -spec: - image: 099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20200528 - machineType: c3.xlarge - maxSize: 15 - minSize: 2 - mixedInstancesPolicy: - instances: - - c3.xlarge - - c4.xlarge - - c5.xlarge - - c5a.xlarge - onDemandAboveBase: 0 - onDemandBase: 0 - spotAllocationStrategy: capacity-optimized - nodeLabels: - kops.k8s.io/instancegroup: spotgroup - role: Node - subnets: - - us-east-1a - - us-east-1b - - us-east-1c -``` - -you will need to add the following: - -``` - additionalUserData: - - name: docker-registry-proxy.sh - type: text/x-shellscript - content: | - #!/bin/sh - - # Add environment vars pointing Docker to use the proxy - # https://docs.docker.com/config/daemon/systemd/#httphttps-proxy - - mkdir -p /etc/systemd/system/docker.service.d - cat << EOD > /etc/systemd/system/docker.service.d/http-proxy.conf - [Service] - Environment="HTTP_PROXY=http://docker-registry-proxy.:3128/" - Environment="HTTPS_PROXY=http://docker-registry-proxy.:3128/" - EOD - - # Get the CA certificate from the proxy and make it a trusted root. - curl http://docker-registry-proxy.:3128/ca.crt > /usr/share/ca-certificates/docker_registry_proxy.crt - echo "docker_registry_proxy.crt" >> /etc/ca-certificates.conf - update-ca-certificates --fresh - - # Reload systemd - systemctl daemon-reload - - # Restart dockerd - systemctl restart docker.service -``` - -so the final InstanceGroup will look like this: - -``` -apiVersion: kops.k8s.io/v1alpha2 -kind: InstanceGroup -metadata: - labels: - kops.k8s.io/cluster: spot.k8s.local - name: spotgroup -spec: - additionalUserData: - - name: docker-registry-proxy.sh - type: text/x-shellscript - content: | - #!/bin/sh - - # Add environment vars pointing Docker to use the proxy - # https://docs.docker.com/config/daemon/systemd/#httphttps-proxy - - mkdir -p /etc/systemd/system/docker.service.d - cat << EOD > /etc/systemd/system/docker.service.d/http-proxy.conf - [Service] - Environment="HTTP_PROXY=http://docker-registry-proxy.:3128/" - Environment="HTTPS_PROXY=http://docker-registry-proxy.:3128/" - EOD - - # Get the CA certificate from the proxy and make it a trusted root. - curl http://docker-registry-proxy.:3128/ca.crt > /usr/share/ca-certificates/docker_registry_proxy.crt - echo "docker_registry_proxy.crt" >> /etc/ca-certificates.conf - update-ca-certificates --fresh - - # Reload systemd - systemctl daemon-reload - - # Restart dockerd - systemctl restart docker.service - image: 099720109477/ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-20200528 - machineType: c3.xlarge - maxSize: 15 - minSize: 2 - mixedInstancesPolicy: - instances: - - c3.xlarge - - c4.xlarge - - c5.xlarge - - c5a.xlarge - onDemandAboveBase: 0 - onDemandBase: 0 - spotAllocationStrategy: capacity-optimized - nodeLabels: - kops.k8s.io/instancegroup: spotgroup - role: Node - subnets: - - us-east-1a - - us-east-1b - - us-east-1c -``` - -Now all you need is to upgrade your cluster and do a rolling-update of the nodes, all images will be cached from now on. diff --git a/docs/kops/docker-registry-proxy.yaml b/docs/kops/docker-registry-proxy.yaml deleted file mode 100644 index 48e6b34..0000000 --- a/docs/kops/docker-registry-proxy.yaml +++ /dev/null @@ -1,81 +0,0 @@ ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: docker-registry-proxy - namespace: registry-mirrors - labels: - app.kubernetes.io/name: docker-registry-proxy -spec: - serviceName: docker-registry - selector: - matchLabels: - app.kubernetes.io/name: docker-registry-proxy - template: - metadata: - labels: - app.kubernetes.io/name: docker-registry-proxy - spec: - serviceAccountName: default - containers: - - name: docker-registry-proxy - image: ghcr.io/rpardini/docker-registry-proxy:0.6.1 - imagePullPolicy: IfNotPresent - env: - - name: ENABLE_MANIFEST_CACHE - value: "true" - - name: REGISTRIES - value: "k8s.gcr.io gcr.io quay.io us.gcr.io" - ports: - - name: http - containerPort: 3128 - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http - volumeMounts: - - name: ca - mountPath: /ca - - name: docker-registry-cache - mountPath: /docker_mirror_cache - resources: {} - volumeClaimTemplates: - - metadata: - name: ca - spec: - accessModes: ["ReadWriteOnce"] - resources: - requests: - storage: 1Gi - - metadata: - name: docker-registry-cache - spec: - accessModes: ["ReadWriteOnce"] - resources: - requests: - storage: 100Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: docker-registry-proxy - namespace: registry-mirrors - labels: - app.kubernetes.io/name: docker-registry-proxy - annotations: - external-dns.alpha.kubernetes.io/hostname: docker-registry-proxy. - service.beta.kubernetes.io/aws-load-balancer-internal: "true" -spec: - type: LoadBalancer - ports: - - port: 3128 - targetPort: http - protocol: TCP - name: http - selector: - app.kubernetes.io/name: docker-registry-proxy diff --git a/entrypoint.sh b/entrypoint.sh index e14aa9f..461a3da 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -42,15 +42,15 @@ fi ALLDOMAINS="" # Interceptions map, which are the hosts that will be handled by the caching part. -# It should list exactly the same hosts we have created certificates for -- if not, Docker will get TLS errors, of course. -echo -n "" > /etc/nginx/docker.intercept.map +# It should list exactly the same hosts we have created certificates for -- if not, client will get TLS errors, of course. +echo -n "" > /etc/nginx/registry.proxy.intercept.map -# Some hosts/registries are always needed, but others can be configured in env var REGISTRIES -for ONEREGISTRYIN in docker.caching.proxy.internal registry-1.docker.io auth.docker.io ${REGISTRIES}; do +# Hosts/registries are configured in env var REGISTRIES +for ONEREGISTRYIN in ${REGISTRIES}; do ONEREGISTRY=$(echo ${ONEREGISTRYIN} | xargs) # Remove whitespace echo "Adding certificate for registry: $ONEREGISTRY" ALLDOMAINS="${ALLDOMAINS},DNS:${ONEREGISTRY}" - echo "${ONEREGISTRY} 127.0.0.1:443;" >> /etc/nginx/docker.intercept.map + echo "${ONEREGISTRY} 127.0.0.1:443;" >> /etc/nginx/registry.proxy.intercept.map done # Clean the list and generate certificates. @@ -59,10 +59,10 @@ export ALLDOMAINS=${ALLDOMAINS:1} # remove the first comma and export # Target host interception. Empty by default. Used to intercept outgoing requests # from the proxy to the registries. -echo -n "" > /etc/nginx/docker.targetHost.map +echo -n "" > /etc/nginx/registry.proxy.targetHost.map # Now handle the auth part. -echo -n "" > /etc/nginx/docker.auth.map +echo -n "" > /etc/nginx/registry.proxy.auth.map # Only configure auth registries if the env var contains values if [ "$AUTH_REGISTRIES" ]; then @@ -89,7 +89,7 @@ if [ "$AUTH_REGISTRIES" ]; then AUTH_PASS="${registry_array[2]}" AUTH_BASE64=$(echo -n ${AUTH_USER}:${AUTH_PASS} | base64 -w0 | xargs) echo "Adding Auth for registry '${AUTH_HOST}' with user '${AUTH_USER}'." - echo "\"${AUTH_HOST}\" \"${AUTH_BASE64}\";" >> /etc/nginx/docker.auth.map + echo "\"${AUTH_HOST}\" \"${AUTH_BASE64}\";" >> /etc/nginx/registry.proxy.auth.map done fi @@ -97,13 +97,13 @@ fi echo " listen 443 ssl default_server;" > /etc/nginx/caching.layer.listen echo "error_log /var/log/nginx/error.log warn;" > /etc/nginx/error.log.debug.warn -# Set Docker Registry cache size, by default, 32 GB ('32g') +# Set Registry cache size, by default, 32 GB ('32g') CACHE_MAX_SIZE=${CACHE_MAX_SIZE:-32g} -# Set cache directory location, by default, /docker_mirror_cache -CACHE_DIRECTORY=${CACHE_DIRECTORY:-/docker_mirror_cache} +# Set cache directory location, by default, /registry_proxy_mirror_cache +CACHE_DIRECTORY=${CACHE_DIRECTORY:-/registry_proxy_mirror_cache} -# The cache directory. This can get huge. Better to use a Docker volume pointing here! +# The cache directory. This can get huge. Better to use a Podman volume pointing here! # Set to 32gb which should be enough echo "proxy_cache_path ${CACHE_DIRECTORY} levels=1:2 max_size=${CACHE_MAX_SIZE:-15g} min_free=${CACHE_MIN_FREE:-1g} inactive=${CACHE_INACTIVE_TIME:-60d} keys_zone=cache:${CACHE_KEYS_ZONE:-15m} use_temp_path=off manager_threshold=${CACHE_MANAGER_THRESHOLD:-1000ms} manager_sleep=${CACHE_MANAGER_SLEEP:-250ms} manager_files=${CACHE_MANAGER_FILES:-100} loader_files=${CACHE_LOADER_FILES:-100} loader_threshold=${CACHE_LOADER_THRESHOLD:-200ms} loader_sleep=${CACHE_LOADER_SLEEP:-50ms};" > /etc/nginx/conf.d/cache_max_size.conf @@ -140,7 +140,7 @@ if [[ ! ("${free_space}" -gt "${min_free}") ]]; then rm -rf "${CACHE_DIRECTORY:?}"/* fi -# Set Docker Registry cache valid time, by default, 60 day ('60d') +# Set Container Registry cache valid time, by default, 60 day ('60d') CACHE_VALID_TIME=${CACHE_VALID_TIME:-60d} # Set default cache valid time for 200 and 205 response. @@ -152,7 +152,7 @@ echo -n "" >/etc/nginx/nginx.manifest.caching.config.conf [[ "a${ENABLE_MANIFEST_CACHE}" == "atrue" ]] && [[ "a${MANIFEST_CACHE_PRIMARY_REGEX}" != "a" ]] && cat <>/etc/nginx/nginx.manifest.caching.config.conf # First tier caching of manifests; configure via MANIFEST_CACHE_PRIMARY_REGEX and MANIFEST_CACHE_PRIMARY_TIME location ~ ^/v2/(.*)/manifests/${MANIFEST_CACHE_PRIMARY_REGEX} { - set \$docker_proxy_request_type "manifest-primary"; + set \$registry_proxy_request_type "manifest-primary"; set \$cache_key \$uri; proxy_cache_valid ${MANIFEST_CACHE_PRIMARY_TIME}; include "/etc/nginx/nginx.manifest.stale.conf"; @@ -162,7 +162,7 @@ EOD [[ "a${ENABLE_MANIFEST_CACHE}" == "atrue" ]] && [[ "a${MANIFEST_CACHE_SECONDARY_REGEX}" != "a" ]] && cat <>/etc/nginx/nginx.manifest.caching.config.conf # Secondary tier caching of manifests; configure via MANIFEST_CACHE_SECONDARY_REGEX and MANIFEST_CACHE_SECONDARY_TIME location ~ ^/v2/(.*)/manifests/${MANIFEST_CACHE_SECONDARY_REGEX} { - set \$docker_proxy_request_type "manifest-secondary"; + set \$registry_proxy_request_type "manifest-secondary"; set \$cache_key \$uri; proxy_cache_valid ${MANIFEST_CACHE_SECONDARY_TIME}; include "/etc/nginx/nginx.manifest.stale.conf"; @@ -172,7 +172,7 @@ EOD [[ "a${ENABLE_MANIFEST_CACHE}" == "atrue" ]] && cat <>/etc/nginx/nginx.manifest.caching.config.conf # Default tier caching for manifests. Caches for ${MANIFEST_CACHE_DEFAULT_TIME} (from MANIFEST_CACHE_DEFAULT_TIME) location ~ ^/v2/(.*)/manifests/ { - set \$docker_proxy_request_type "manifest-default"; + set \$registry_proxy_request_type "manifest-default"; set \$cache_key \$uri; proxy_cache_valid ${MANIFEST_CACHE_DEFAULT_TIME}; include "/etc/nginx/nginx.manifest.stale.conf"; @@ -182,7 +182,7 @@ EOD [[ "a${ENABLE_MANIFEST_CACHE}" != "atrue" ]] && cat <>/etc/nginx/nginx.manifest.caching.config.conf # Manifest caching is disabled. Enable it with ENABLE_MANIFEST_CACHE=true location ~ ^/v2/(.*)/manifests/ { - set \$docker_proxy_request_type "manifest-default-disabled"; + set \$registry_proxy_request_type "manifest-default-disabled"; set \$cache_key \$uri; proxy_cache_valid 0s; include "/etc/nginx/nginx.manifest.stale.conf"; @@ -255,48 +255,6 @@ fi # normally use non-debug version of nginx NGINX_BIN="/usr/sbin/nginx" -if [[ "a${DEBUG}" == "atrue" ]]; then - if [[ ! -f /usr/bin/mitmweb ]]; then - echo "To debug, you need the -debug version of this image, eg: :latest-debug" - exit 3 - fi - - # in debug mode, change caching layer to listen on 444, so that mitmproxy can sit in the middle. - echo " listen 444 ssl default_server;" > /etc/nginx/caching.layer.listen - - echo "Starting in DEBUG MODE (mitmproxy)." >&2 - echo "Run mitmproxy with reverse pointing to the same certs..." - mitmweb --no-web-open-browser --set web_host=0.0.0.0 --set confdir=~/.mitmproxy-incoming \ - --set termlog_verbosity=error --set stream_large_bodies=128k --web-port 8081 \ - --set keep_host_header=true --set ssl_insecure=true \ - --mode reverse:https://127.0.0.1:444 --listen-host 0.0.0.0 \ - --listen-port 443 --certs /certs/fullchain_with_key.pem & - echo "Access mitmweb via http://127.0.0.1:8081/ " -fi - -if [[ "a${DEBUG_HUB}" == "atrue" ]]; then - if [[ ! -f /usr/bin/mitmweb ]]; then - echo "To debug, you need the -debug version of this image, eg: :latest-debug" - exit 3 - fi - - # in debug hub mode, we remap targetHost to point to mitmproxy below - echo "\"registry-1.docker.io\" \"127.0.0.1:445\";" > /etc/nginx/docker.targetHost.map - - echo "Debugging outgoing DockerHub connections via mitmproxy on 8082." >&2 - # this one has keep_host_header=false so we don't need to modify nginx config - mitmweb --no-web-open-browser --set web_host=0.0.0.0 --set confdir=~/.mitmproxy-outgoing-hub \ - --set termlog_verbosity=error --set stream_large_bodies=128k --web-port 8082 \ - --set keep_host_header=false --set ssl_insecure=true \ - --mode reverse:https://registry-1.docker.io --listen-host 0.0.0.0 \ - --listen-port 445 --certs /certs/fullchain_with_key.pem & - - echo "Warning, DockerHub outgoing debugging disables upstream SSL verification for all upstreams." >&2 - VERIFY_SSL=false - - echo "Access mitmweb for outgoing DockerHub requests via http://127.0.0.1:8082/ " -fi - if [[ "a${DEBUG_NGINX}" == "atrue" ]]; then if [[ ! -f /usr/sbin/nginx-debug ]]; then echo "To debug, you need the -debug version of this image, eg: :latest-debug" @@ -337,21 +295,21 @@ cat /etc/nginx/nginx.timeouts.config.conf echo -e "---\n" # Request buffering -echo "" > /etc/nginx/proxy.buffering.conf +echo "" > /etc/nginx/registry.proxy.buffering.conf if [[ "a${PROXY_BUFFERING}" == "afalse" ]]; then - cat << EOD > /etc/nginx/proxy.buffering.conf + cat << EOD > /etc/nginx/registry.proxy.buffering.conf proxy_buffering off; EOD fi echo -e "\nBuffering: ---" -cat /etc/nginx/proxy.buffering.conf +cat /etc/nginx/registry.proxy.buffering.conf echo -e "---\n" # Request buffering -echo "" > /etc/nginx/proxy.request.buffering.conf +echo "" > /etc/nginx/registry.proxy.request.buffering.conf if [[ "a${PROXY_REQUEST_BUFFERING}" == "afalse" ]]; then - cat << EOD > /etc/nginx/proxy.request.buffering.conf + cat << EOD > /etc/nginx/registry.proxy.request.buffering.conf proxy_max_temp_file_size 0; proxy_request_buffering off; proxy_http_version 1.1; @@ -359,7 +317,7 @@ EOD fi echo -e "\nRequest buffering: ---" -cat /etc/nginx/proxy.request.buffering.conf +cat /etc/nginx/registry.proxy.request.buffering.conf echo -e "---\n" # force upstream to use http 1.1 @@ -377,9 +335,9 @@ cat /etc/nginx/http1.1.upstream.conf echo -e "---\n" # Upstream SSL verification. -echo "" > /etc/nginx/docker.verify.ssl.conf +echo "" > /etc/nginx/registry.proxy.verify.ssl.conf if [[ "a${VERIFY_SSL}" == "atrue" ]]; then - cat << EOD > /etc/nginx/docker.verify.ssl.conf + cat << EOD > /etc/nginx/registry.proxy.verify.ssl.conf # We actually wanna be secure and avoid mitm attacks. # Fitting, since this whole thing is a mitm... # We'll accept any cert signed by a CA trusted by Mozilla (ca-certificates-bundle in alpine) diff --git a/nginx.conf b/nginx.conf index 4ddfe56..bff3e6f 100644 --- a/nginx.conf +++ b/nginx.conf @@ -75,7 +75,7 @@ http { '"upstream_cache_status":"$upstream_cache_status",' '"method":"$request_method",' '"uri":"$uri",' - '"request_type":"$docker_proxy_request_type",' + '"request_type":"$registry_proxy_request_type",' '"status":"$status",' '"bytes_sent":"$body_bytes_sent",' '"upstream_response_time":"$upstream_response_time",' @@ -92,25 +92,25 @@ http { # Just in case you want to rewrite some hosts. Default maps directly. map $host $targetHost { hostnames; - include /etc/nginx/docker.targetHost.map; + include /etc/nginx/registry.proxy.targetHost.map; default $host; } - # A map to enable authentication to some specific docker registries. + # A map to enable authentication to specific registries. # This is auto-generated by the entrypoint.sh based on environment variables - map $host $dockerAuth { + map $host $registryAuth { hostnames; - include /etc/nginx/docker.auth.map; + include /etc/nginx/registry.proxy.auth.map; default ""; } - # @TODO: actually for auth.docker.io, if we want to support multiple authentications, we'll need to decide + # @TODO: 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.... + # @TODO: I wonder if this would help gcr.io and quay.io with authentication.... - 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 $registryAuth $finalAuth { + "" "$http_authorization"; # if empty, keep the original passed-in from the client. + default "Basic $registryAuth"; # if not empty, add the Basic preamble to the auth } @@ -119,7 +119,7 @@ http { # By default, we don't intercept, allowing free flow of non-registry traffic map $connect_host $interceptedHost { hostnames; - include /etc/nginx/docker.intercept.map; + include /etc/nginx/registry.proxy.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.' } @@ -147,7 +147,7 @@ http { # dont log the CONNECT proxy. #access_log /var/log/nginx/access.log debug_proxy; access_log off; - set $docker_proxy_request_type "unknown-connect"; + set $registry_proxy_request_type "unknown-connect"; proxy_connect; proxy_connect_allow all; @@ -161,7 +161,7 @@ http { # 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!"; + return 200 "container-registry-proxy: The registry caching proxy is working!"; } location /ca.crt { @@ -178,18 +178,18 @@ if [ ! -d /etc/systemd ]; then exit 1 fi -mkdir -p /etc/systemd/system/docker.service.d -cat << EOD > /etc/systemd/system/docker.service.d/http-proxy.conf +mkdir -p /usr/lib/systemd/system/podman.service.d +cat << EOD > /usr/lib/systemd/system/podman.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 +curl $scheme://$http_host/ca.crt > /usr/share/ca-certificates/container_registry_proxy.crt +if fgrep -q "container_registry_proxy.crt" /etc/ca-certificates.conf ; then echo "certificate refreshed" else - echo "docker_registry_proxy.crt" >> /etc/ca-certificates.conf + echo "container_registry_proxy.crt" >> /etc/ca-certificates.conf fi update-ca-certificates --fresh @@ -197,9 +197,9 @@ update-ca-certificates --fresh # Reload systemd systemctl daemon-reload -# Restart dockerd -systemctl restart docker.service -echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" +# Restart podman.service +systemctl restart podman.service +echo "Podman configured with HTTPS_PROXY=$scheme://$http_host/" '; } # end location /setup/systemd } # end server @@ -215,10 +215,10 @@ echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" # Do some tweaked logging. access_log /var/log/nginx/access.log tweaked; - set $docker_proxy_request_type "unknown"; + set $registry_proxy_request_type "unknown"; # Send upstream status as header - add_header X-Docker-Registry-Proxy-Cache-Upstream-Status "$upstream_cache_status"; + add_header X-Container-Registry-Proxy-Cache-Upstream-Status "$upstream_cache_status"; # Use the generated certificates, they contain names for all the proxied registries. ssl_certificate /certs/fullchain.pem; @@ -228,7 +228,7 @@ echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" #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. + # We need this. Don't ask. chunked_transfer_encoding on; # configuration of the different allowed methods @@ -237,12 +237,12 @@ echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" proxy_read_timeout 900; # Buffering - include /etc/nginx/proxy.buffering.conf; + include /etc/nginx/registry.proxy.buffering.conf; # Request buffering - include /etc/nginx/proxy.request.buffering.conf; + include /etc/nginx/registry.proxy.request.buffering.conf; - # Use cache locking, with a huge timeout, so that multiple Docker clients asking for the same blob at the same time + # Use cache locking, with a huge timeout, so that multiple 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; @@ -272,25 +272,25 @@ echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" include /etc/nginx/http1.1.upstream.conf; # This comes from a include file generated by the entrypoint. - include /etc/nginx/docker.verify.ssl.conf; + include /etc/nginx/registry.proxy.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. + # 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"; + return 405 "container-registry-proxy: client is trying to use v1 API. Either the image does not exist upstream, or you need to configure container-registry-proxy to authenticate against $host"; } # For blob requests by digest, do cache, and treat redirects. location ~ ^/v2/(.*)/blobs/sha256:(.*) { - set $docker_proxy_request_type "blob-by-digest"; + set $registry_proxy_request_type "blob-by-digest"; set $cache_key $request_method$2; include "/etc/nginx/nginx.manifest.common.conf"; } # For manifest requests by digest, do cache, and treat redirects. - # These are some of the requests that DockerHub will throttle. + # These are some of the requests that some registries will throttle. location ~ ^/v2/(.*)/manifests/sha256:(.*) { - set $docker_proxy_request_type "manifest-by-digest"; + set $registry_proxy_request_type "manifest-by-digest"; set $cache_key $request_method$uri; include "/etc/nginx/nginx.manifest.common.conf"; } @@ -303,7 +303,7 @@ echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" # Cache blobs requests that are not by digest # Since these are mutable, we invalidate them immediately and keep them only in case the backend is down location ~ ^/v2/(.*)/blobs/ { - set $docker_proxy_request_type "blob-mutable"; + set $registry_proxy_request_type "blob-mutable"; set $cache_key $request_method$uri; proxy_cache_valid 0s; include "/etc/nginx/nginx.manifest.stale.conf"; @@ -331,7 +331,7 @@ echo "Docker configured with HTTPS_PROXY=$scheme://$http_host/" # But we store the result with the cache key of the original request URI # so that future clients don't need to follow the redirect too proxy_cache_key $cache_key$slice_range; - add_header X-Docker-Registry-Proxy-Cache-Key-Status "$cache_key$slice_range"; + add_header X-Container-Registry-Proxy-Cache-Key-Status "$cache_key$slice_range"; } # by default, dont cache anything. diff --git a/nginx.manifest.common.conf b/nginx.manifest.common.conf index 0d34293..ee44504 100644 --- a/nginx.manifest.common.conf +++ b/nginx.manifest.common.conf @@ -1,12 +1,12 @@ # nginx config fragment included in every manifest-related location{} block. - add_header X-Docker-Registry-Proxy-Cache-Upstream-Status "$upstream_cache_status"; - add_header X-Docker-Registry-Proxy-Cache-Type "$docker_proxy_request_type"; + add_header X-Container-Registry-Proxy-Cache-Upstream-Status "$upstream_cache_status"; + add_header X-Container-Registry-Proxy-Cache-Type "$registry_proxy_request_type"; proxy_pass https://$targetHost; proxy_cache $cache; slice 4m; proxy_cache_key $cache_key$slice_range; proxy_set_header Range $slice_range; - add_header X-Docker-Registry-Proxy-Cache-Key-Status "$cache_key$slice_range"; + add_header X-Container-Registry-Proxy-Cache-Key-Status "$cache_key$slice_range"; proxy_http_version 1.1; proxy_intercept_errors on; error_page 301 302 307 = @handle_redirects; diff --git a/package.json b/package.json index ee0c561..b1e7f10 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "docker-registry-proxy", + "name": "container-registry-proxy", "dependencies": { "@semantic-release/exec": "^5.0.0", "@semantic-release/github": "^7.2.0", "semantic-release": "^17.3.9" } -} \ No newline at end of file +}