{"id":14235792,"url":"https://github.com/jpetazzo/registrish","last_synced_at":"2025-07-25T15:15:31.876Z","repository":{"id":43659002,"uuid":"288797866","full_name":"jpetazzo/registrish","owner":"jpetazzo","description":"Dirty hack to run a read-only, public Docker registry on almost any static file hosting service (e.g. NGINX, Netlify, S3...)","archived":false,"fork":false,"pushed_at":"2025-02-22T14:20:36.000Z","size":30,"stargazers_count":165,"open_issues_count":1,"forks_count":6,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-07-15T07:30:20.144Z","etag":null,"topics":["containers","docker","kubernetes","registry"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jpetazzo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-19T17:43:11.000Z","updated_at":"2025-07-09T13:07:35.000Z","dependencies_parsed_at":"2023-01-30T00:15:46.036Z","dependency_job_id":null,"html_url":"https://github.com/jpetazzo/registrish","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jpetazzo/registrish","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpetazzo%2Fregistrish","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpetazzo%2Fregistrish/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpetazzo%2Fregistrish/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpetazzo%2Fregistrish/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jpetazzo","download_url":"https://codeload.github.com/jpetazzo/registrish/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jpetazzo%2Fregistrish/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267023278,"owners_count":24022924,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-25T02:00:09.625Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["containers","docker","kubernetes","registry"],"created_at":"2024-08-20T21:02:21.675Z","updated_at":"2025-07-25T15:15:31.850Z","avatar_url":"https://github.com/jpetazzo.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":"# Registrish\n\nThis is *kind of* a Docker registry, but with many restrictions:\n\n- it's read-only (you can `pull` but you cannot `push`)\n- it only supports public access (no authentication)\n- it only supports a subset of the Docker Distribution API\n\nThe last point means that pulls from registrish will hopefully work,\nbut might break in unexpected ways. See [Limitations] below for more info.\n\nOn the bright side, registrish can be deployed without running\nthe registry code, using almost any static file hosting service.\nFor instance:\n\n- a plain NGINX server (without LUA, JSX, or whatever custom module)\n- a plain Apache2 server (using `.htaccess` overrides)\n- the [Netlify] CDN\n- an object store like S3, or a compatible one like [R2] or [Scaleway]\n\n\n## Example\n\nThe following commands will run an Alpine image hosted on various\nlocations, thanks to registrish.\n\nNetlify:\n```bash\ndocker run registrish.netlify.app/alpine echo hello there\n```\n\nR2:\n```bash\ndocker run pub-a8f6c50314a0467b8f862a261a950bcf.r2.dev/alpine echo hello there\n```\n\nS3:\n```bash\ndocker run registrish.s3.amazonaws.com/alpine echo hello there\n```\n\nScaleway object store:\n```bash\ndocker run registrish.s3.fr-par.scw.cloud/alpine echo hello there\n```\n\n\n## Hosting your images with registrish\n\nIn the following example, we are going to host the official image\n`alpine:latest` with registrish.\n\nLet's set a couple of env vars for convenience:\n\n```bash\nexport DIR=tmp IMAGE=alpine TAG=latest\n```\n\nLet's obtain the manifests and blobs of the image. This requires [Skopeo].\n\n```bash\nskopeo copy --all docker://$IMAGE:$TAG dir:$DIR\n```\n\nThe `--all` flag means that we want to obtain a *manifest list*\n(i.e a multi-arch image), if one is available.\n\nThen, we're going to move the files downloaded by Skopeo to their\nrespective directories. Blobs go to the `blobs` directory, and manifests\ngo to the `manifests` directory. All files get renamed to `sha256:xxx`\nwhere `xxx` is their SHA256 checksum. The top-level manifest also gets\ncopied to the tag name to allow pulling by tag.\n\n```bash\n./dir2reg.sh\n```\n\nYou can check that everything looks fine with the `tree` command:\n\n```bash\ntree v2\n```\n\nThere should be:\n\n- a bunch of `sha256:xxx` files in `blobs`,\n- a bunch of `sha256:xxx` files in `manifests`,\n- one tag file (e.g. `latest`) in `manifests`.\n\nThen, pick a registrish back-end and follow its specific instructions.\n\n\n### NGINX server\n\nGenerate the NGINX configration file. The configuration file will\nset `content-type` HTTP headers.\n\n```bash\n./gen-nginx.sh\n```\n\nStart NGINX in a local Docker container. It will be on port 5555.\n\n```bash\ndocker-compose up\n```\n\nThe image will be available as `localhost:5555/$IMAGE:$TAG`.\n\n\n### Apache2 server\n\nGenerate a `.htaccess` file in `./v2/` folder. This configuration file will\nset `content-type` HTTP headers.\n\n```bash\n./gen-apache2.sh\n```\n\nYou may upload `./v2/` folder to the server root of an existing Apache2 server.\nModule `headers_module` must be available and `.htaccess` overrides enabled in\nserver configuration.\n\n\n### Netlify\n\nGenerate the Netlify headers file.\n\n```bash\n./gen-netlify.sh\n```\n\nTo deploy to Netlify, it's better to deploy only the `v2` directory and the `_headers` file:\n\n```bash\nTEMP=$(mktemp -d)\ncp -a v2 _headers $TEMP\nnpx netlify deploy --dir $TEMP --prod\n```\n\n\n### S3 bucket or compatible\n\nThe following assumes that you have configured your S3 credentials,\nfor instance by setting `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`\nenvironment variables.\n\nUpdate this variable with your bucket name.\n```bash\nexport BUCKET=registrish\n```\n\nIf you are using an S3-compatible API, you need to set a few\nadditional variables (or set the corresponding parameters in your\nprofile).\n\n- R2:\n  ```bash\n  export CLOUDFLARE_ACCOUNT_ID=c4c9...\n  export ENDPOINT=\"--endpoint-url https://$CLOUDFLARE_ACCOUNT_ID.r2.cloudflarestorage.com\"\n  ```\n\n- Scaleway:\n  ```bash\n  export AWS_DEFAULT_REGION=fr-par\n  export ENDPOINT=\"--endpoint-url https://s3.fr-par.scw.cloud\"\n  ```\n\nSync files to the bucket.\n\n```bash\n./reg2bucket.sh\n```\n\nThe image will be available as `$BUCKET.s3.amazonaws.com/$IMAGE:$TAG`\n(for S3) or `$BUCKETNAME.$ENDPOINT/$IMAGE:$TAG` (for compatible APIs).\n\n\n## Testing\n\nWhen testing registrish, we want to be sure that the entire image\nwill be pulled correctly with all its layers. If we try on our local\ncontainer engine, we might already have some manifests and layers.\n\nOne way to test that is to use a throwaway Docker-in-Docker container.\n\n```bash\ndocker run --name dind -d --privileged --net host docker:dind\ndocker exec dind docker pull REGISTRISH-IMAGE\ndocker rm -f dind\n```\n\n\n## How it works\n\nThe Docker Registry is *almost* a static web server.\nThe main trick is to handle `Content-Type` headers correctly.\n\nAs far as I understand, this is what happens when a container engine\ntries to pull an image by tag.\n\n- First, the engine wants to know the hash of the manifest that we're\n  trying to pull.\n- To learn that hash, the engine makes a `HEAD` request on\n  `/v2/\u003cimage\u003e/manifests/\u003ctag\u003e`.\n  - If the registry sends a `Docker-Content-Digest` header (which will\n    look like `sha256:\u003cxxx\u003e`), that header is the hash, so the engine\n    can go to the next step.\n  - If the registry doesn't send a `Docker-Content-Digest` header,\n    the engine makes a `GET` request on `/v2/\u003cimage\u003e/manifests/\u003ctag\u003e`,\n    computes the SHA256 checksum of the response body (let's say it's\n    `\u003cxxx\u003e` to match the previous example). Now the engine has the hash\n    (at the cost of an extra HTTP request).\n- The engine makes a request to `/v2/\u003cimage\u003e/manifests/sha256:\u003cxxx\u003e`.\n  The `Content-Type` will indicate if we're dealing with a v2 manifest\n  (single-arch image) or a v2 manifest list (multi-arch image).\n  - If it's a v2 manifest, we can use it directly.\n  - If it's a manifest list, it contains a list of manifests. Each entry\n    has a platform (e.g. `linux/amd64`) and a hash (for instance `sha256:\u003cyyy\u003e`).\n    The engine picks the entry that it deems appropriate (because it\n    matches its architecture, or one that it's compatible with) and it\n    requests the corresponding manifest, on `/v2/\u003cimage\u003e/manifests/sha256:\u003cyyy\u003e`.\n    *That* manifest should be a v2 manifest.\n- The engine now has a v2 manifest. In the v2 manifest, there is a list\n  of *blobs*. One of these blobs will be a JSON file containing\n  the configuration of the image (entry point, command,\n  volumes, etc.) and the other ones will be the layers\n  of the images. The blobs are stored in\n  `/v2/\u003cimage\u003e/blobs/sha256:\u003csha-of-the-blob\u003e`.\n\nAs long as we use the correct `Content-Type` when serving image manifests,\nthe container engine should be happy. *Unless...*\n\n\n### Limitations\n\n*Unless* the container engine explicitly asks a specific type of\nmanifests, which it can do by using `Accept` request headers.\nIf the engine asks for a v2 manifest (single-arch) and we serve\na v2 manifest list (multi-arch), I expect that it will complain loudly.\n\nI don't understand why my former colleagues at Docker decided\nto go with this scheme, instead of e.g. keeping v2 manifests in `/manifests`\nand storing the multi-arch manifest lists in e.g. `/lists`.\nIt would have avoided using HTTP headers to alter the content served\nby the registry. 🤷🏻\n\nI also don't understand the point of that `HEAD` request and custom\n`Docker-Content-Digest` HTTP header. The same result could have been\nachieved with an explicit HTTP route to resolve tags to hashes. 🤷🏻\n\n\n## Notes\n\nNetlify is very fast to serve web pages, but not so much\nto serve binary blobs. (I suspect that they throttle them\non purpose to prevent abuse, but that's just an intuition.)\n\nTheir [terms of service] state the following (in August 2020):\n\n\u003e Users must exercise caution when hosting large downloads (\u003e10MB).\n\u003e Netlify reserves the right to refuse to host any large downloadable files.\n\n\n## Providers and object stores that might not work\n\nI've tried to use [OVHcloud] object storage, but it\ndoesn't seem to be easy to do so. OVHcloud storage buckets\nhave very long URLs like\nhttps://storage.gra.cloud.ovh.net/v1/AUTH_xxx-long-tenant-id-xxx/bucketname\nand the Docker registry protocol doesn't support uppercase\ncharacters in image names.\n\nIt's possible to use custom domain names, but then there\nare certificate issues. If you know an easy way to make\nit work, let me know!\n\n\n## TODO\n\n- add 404 page on Netlify\n- try CloudFlare R2 as soon as it's out :)\n\n\n## Similar work and prior art\n\n- [NicolasT/static-container-registry](https://github.com/NicolasT/static-container-registry)\n- [singularityhub/registry](https://github.com/singularityhub/registry)\n\n\n[Limitations]: #limitations\n[Netlify]: http://netlify.com/\n[OVHcloud]: https://www.ovhcloud.com/en/public-cloud/prices/#storage\n[R2]: https://developers.cloudflare.com/r2/\n[Scaleway]: https://www.scaleway.com/en/pricing/#object-storage\n[Skopeo]: https://github.com/containers/skopeo\n[terms of service]: https://www.netlify.com/tos/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpetazzo%2Fregistrish","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjpetazzo%2Fregistrish","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjpetazzo%2Fregistrish/lists"}