Lightweight local HTTP registry for uploading, publishing, and serving binaries and files
259
A self-hosted HTTP registry for goreleaser releases.
goreleaser builds your archives, binaries, checksums and its artifacts.json / metadata.json catalogue. local-registry stores them and serves
them over HTTP, on your machine, your LAN or a private server, without GitHub or GitLab.
artifacts.json are checked first. A
published version is immutable.artifacts.json points to those URLs, not to the paths on the build
machine.artifacts.json, metadata.json, list of files with their URLs).Source and full documentation: https://github.com/nivekalara237/local-registry
| Host | How |
|---|---|
| Linux x86-64 / ARM64 | Docker Engine (native linux/amd64, linux/arm64 images) |
| macOS Intel / Apple Silicon | Docker Desktop, OrbStack, Colima |
| Windows x64 / ARM64 | Docker Desktop (WSL 2 backend) |
Docker pulls the right architecture automatically. The same image is also published as ghcr.io/nivekalara237/local-registry.
export LOCAL_REGISTRY_UPLOAD_TOKEN="$(openssl rand -hex 32)"
echo "$LOCAL_REGISTRY_UPLOAD_TOKEN" # keep it: clients need it to publish
docker run -d --name local-registry --restart unless-stopped \
-p 8077:8077 \
-v local-registry-data:/data \
-e LOCAL_REGISTRY_UPLOAD_TOKEN \
nivekaacom/local-registry:latest
curl http://localhost:8077/healthz # {"status":"ok"}
$env:LOCAL_REGISTRY_UPLOAD_TOKEN = [Convert]::ToHexString(
[Security.Cryptography.RandomNumberGenerator]::GetBytes(32)).ToLower()
$env:LOCAL_REGISTRY_UPLOAD_TOKEN # keep it: clients need it to publish
docker run -d --name local-registry --restart unless-stopped `
-p 8077:8077 `
-v local-registry-data:/data `
-e LOCAL_REGISTRY_UPLOAD_TOKEN `
nivekaacom/local-registry:latest
Invoke-RestMethod http://localhost:8077/healthz
compose.yaml:
services:
local-registry:
image: nivekaacom/local-registry:latest
restart: unless-stopped
ports:
- "8077:8077"
volumes:
- data:/data
- ./config.yaml:/etc/local-registry/config.yaml:ro # optional, see below
environment:
LOCAL_REGISTRY_CONFIG: /etc/local-registry/config.yaml # optional, see below
LOCAL_REGISTRY_UPLOAD_TOKEN: ${LOCAL_REGISTRY_UPLOAD_TOKEN:?set it in .env}
volumes:
data:
.env, next to compose.yaml, not committed:
LOCAL_REGISTRY_UPLOAD_TOKEN=<64 hex digits>
docker compose up -d
Without LOCAL_REGISTRY_UPLOAD_TOKEN, the registry is read-only: it serves what is already in /data, and the upload routes answer 403.
| Item | Value | Notes |
|---|---|---|
| Port | 8077 | Publish another host port with -p 9000:8077 |
| Data volume | /data | Every release, the staging area and the audit log. Back it up. |
| User | registry, uid/gid 10001 | Never root |
| Health check | GET /healthz every 30 s | Plain HTTP on 127.0.0.1 |
| Command | serve | Also config, version, help |
| Base | alpine:3.24 | Includes ca-certificates, tzdata (set TZ) |
Bind mount instead of a volume. The directory must be writable by uid 10001:
mkdir -p ./registry-data
sudo chown 10001:10001 ./registry-data # Linux only; not needed on Docker Desktop
docker run … -v "$PWD/registry-data:/data" …
On Windows, use -v "${PWD}\registry-data:/data".
A setting is read from the first source that defines it:
… local-registry:latest serve --staging-ttl 48hLOCAL_REGISTRY_<FLAG>: -e LOCAL_REGISTRY_STAGING_TTL=48hLOCAL_REGISTRY_CONFIGThe image already sets
LOCAL_REGISTRY_ASSET_PATH=/data,LOCAL_REGISTRY_ADDRESS=0.0.0.0andLOCAL_REGISTRY_PORT=8077. Environment variables win over the file, soasset-path,addressandportin the YAML file have no effect. Change them with-e.
| Setting (YAML key) | Environment variable | Default | Description |
|---|---|---|---|
| — | LOCAL_REGISTRY_UPLOAD_TOKEN | — | Upload token. Empty: read-only |
public-url | LOCAL_REGISTRY_PUBLIC_URL | — | Address clients use, e.g. https://registry.example.com. Makes download URLs absolute; empty |
| gives relative URLs. Set it behind a proxy or on a server. | |||
max-upload-mb | LOCAL_REGISTRY_MAX_UPLOAD_MB | 200 | Largest uploaded file, MiB |
max-version-mb | LOCAL_REGISTRY_MAX_VERSION_MB | 2048 | Staged bytes per version, MiB (0: no limit) |
max-version-files | LOCAL_REGISTRY_MAX_VERSION_FILES | 100 | Staged files per version (0: no limit) |
max-staging-mb | LOCAL_REGISTRY_MAX_STAGING_MB | 10240 | Staged bytes for all versions, MiB (0: no limit) |
min-free-mb | LOCAL_REGISTRY_MIN_FREE_MB | 1024 | Free disk space an upload must leave, MiB (0: no check) |
staging-ttl | LOCAL_REGISTRY_STAGING_TTL | 24h | Purge unpublished uploads idle this long (0 or ≥ 1h) |
audit-log | LOCAL_REGISTRY_AUDIT_LOG | /data/bin.local/audit.log | Audit log of every upload and publish |
tls-cert, tls-key | LOCAL_REGISTRY_TLS_CERT, LOCAL_REGISTRY_TLS_KEY | — | Serve HTTPS (see below) |
disable-generate-token | LOCAL_REGISTRY_DISABLE_GENERATE_TOKEN | true | false: generate a token at each start and print it in the |
| container logs |
config.yaml:
public-url: https://registry.example.com
max-upload-mb: 500
max-version-mb: 4096
max-version-files: 200
staging-ttl: 48h
min-free-mb: 2048
docker run -d --name local-registry -p 8077:8077 \
-v local-registry-data:/data \
-v "$PWD/config.yaml:/etc/local-registry/config.yaml:ro" \
-e LOCAL_REGISTRY_CONFIG=/etc/local-registry/config.yaml \
-e LOCAL_REGISTRY_UPLOAD_TOKEN \
nivekaacom/local-registry:latest
chmod 644 config.yaml).-e or the .env file rather than in the YAML file.docker exec local-registry local-registry config
This prints every setting, its value and its source (flag, env …, config file, default). The token is never shown, only its token_id.
To list every flag:
docker run --rm nivekaacom/local-registry serve --help
The token travels in clear text over HTTP. On anything other than localhost, use HTTPS.
Recommended: a TLS reverse proxy (Traefik, Caddy, nginx) in front of port 8077, and LOCAL_REGISTRY_PUBLIC_URL=https://….
TLS in the container: mount the certificate and key, readable by uid 10001. Run with --no-healthcheck, because the built-in check uses
plain HTTP.
docker run -d --no-healthcheck -p 8443:8077 \
-v local-registry-data:/data \
-v "$PWD/certs:/certs:ro" \
-e LOCAL_REGISTRY_TLS_CERT=/certs/fullchain.pem \
-e LOCAL_REGISTRY_TLS_KEY=/certs/privkey.pem \
-e LOCAL_REGISTRY_PUBLIC_URL=https://registry.example.com:8443 \
-e LOCAL_REGISTRY_UPLOAD_TOKEN \
nivekaacom/local-registry:latest
In your goreleaser project, after goreleaser release, use scripts/upload.sh. It is shipped in every archive of the GitHub
releases and needs bash, curl and jq.
export LOCAL_REGISTRY_URL=http://localhost:8077
export LOCAL_REGISTRY_UPLOAD_TOKEN=<token>
scripts/upload.sh --latest github dist/artifacts.json
The script uploads every file listed in dist/artifacts.json, artifacts.json last, then publishes the version. github is a free name
(<vcs>) grouping the versions of one product. --latest makes this version the one served by default.
curl http://localhost:8077/github/artifacts # artifacts.json, latest version
curl http://localhost:8077/github/artifacts/v1.2.3 # artifacts.json, given version
curl http://localhost:8077/artifacts/metadatas/github # metadata.json (?version=v1.2.3)
curl http://localhost:8077/artifacts/metadatas/github # metadata.json (?version=v1.2.3)
curl -X POST http://localhost:8077/releases/github/latest # files and their download URLs
curl -OJ http://localhost:8077/github/artifacts/v1.2.3/files/myapp_1.2.3_linux_amd64.tar.gz
docker logs -f local-registry # one line per request; errors carry the exact cause
| Symptom | Fix |
|---|---|
403 on upload | No token set in the container |
401 on upload | Client and server tokens differ |
Container exits with Error: … | Configuration error: the message names the setting |
permission denied under /data | Bind-mounted directory not writable by uid 10001 |
Download URLs point to localhost | Set LOCAL_REGISTRY_PUBLIC_URL |
Images are signed keylessly with cosign (Sigstore, GitHub Actions) and carry an SBOM.
cosign verify nivekaacom/local-registry:<version> \
--certificate-identity-regexp 'https://github.com/nivekalara237/local-registry/.github/workflows/release.yml@.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Content type
Image
Digest
sha256:2634c38b0…
Size
8.4 MB
Last updated
6 days ago
docker pull nivekaacom/local-registry