DEV Community

Cover image for How to use a private Docker registry: from install to login, push, and certificates
Wen Ping
Wen Ping

Posted on Originally published at anguskit.com

How to use a private Docker registry: from install to login, push, and certificates

docker push returns denied, or docker pull fails with x509—usually the image is fine and the registry is not wired: service down, wrong repo type, no login, or the wrong hostname.

This article walks through AngusRepo Community self-hosted in order: install → repos and token → login → push → pull (including CI / Kubernetes). Examples use repo.example.com—swap your domain. Step-by-step denied: push denied troubleshooting.

1. Install (get a working console)

Prerequisites: Docker Engine + Compose v2; about 2 CPU / 4 GB; open 80; resolve gm.example.com and repo.example.com (labs can use /etc/hosts). Full steps: quickstart.

curl --fail --location --progress-bar -o AngusRepo-Community-1.0.0.zip \
  https://repo.anguskit.com/raw/raw-public/AngusKit/repo/AngusRepo-Community-1.0.0.zip
unzip AngusRepo-Community-1.0.0.zip
cd AngusRepo-1.0.0
./install.sh
Enter fullscreen mode Exit fullscreen mode

Wizard aligned with quickstart: Compose; Access → 2 (bundled proxy); TLS → 4 (HTTP :80); Database → Compose MySQL; Public DNS name → suffix such as example.com. After Install finished.:

./bin/angusctl.sh doctor   # wait for doctor: OK
Enter fullscreen mode Exit fullscreen mode

Open http://gm.example.com/, then http://repo.example.com/. Windows: curl.exe + .\install.bat. For HTTPS see Installation.

The rest assumes HTTP self-hosted, so lab Docker usually needs insecure-registries. Once the console opens, create repos and a token before docker login.

2. Repos, token, and image names

Push only to HOSTED; PROXY and GROUP are read-only. On http://repo.example.com/, create (names are examples—keep them consistent):

Example name Type Role
docker-hosted HOSTED Sole docker push target
dockerhub-proxy PROXY Cache Docker Hub (https://registry-1.docker.io; Hub credentials recommended)
docker-group (optional) GROUP One pull entry over the two above

Image form: host/repo/image:tag, e.g. repo.example.com/docker-hosted/myapp:1.0.0.

Do not hand-write /v2. Official Hub images via PROXY/GROUP need library/ (e.g. …/library/nginx:1.27).

Prefer console “connection config”; dedicated ports: Docker format manual.

Also prepare:

  • [ ] Access token (shown once—save it)
  • [ ] Push identity has Write on docker-hosted; pull identity has Read on the entry repo
export REPO_USER="admin"              # console username, not the token name
export REPO_TOKEN="your_access_token" # every password field uses the token
export REPO_HOST="repo.example.com"   # your repo.<domain>
Enter fullscreen mode Exit fullscreen mode

3. Login and certificates

HTTP (matches quickstart)

Without a certificate Docker still tries HTTPS and fails. Add the host to the daemon insecure list and restart:

{
  "insecure-registries": ["repo.example.com"]
}
Enter fullscreen mode Exit fullscreen mode

Then log in:

echo "$REPO_TOKEN" | docker login "$REPO_HOST" \
  --username "$REPO_USER" \
  --password-stdin
Enter fullscreen mode Exit fullscreen mode

Prefer --password-stdin so tokens stay out of shell history.

After you move to HTTPS

Remove the host from insecure-registries and docker login again. Incomplete chains still produce x509: certificate signed by unknown authority.

Symptom Check first
x509 / handshake failure Missing insecure-registries on HTTP; incomplete chain on HTTPS
unauthorized / authentication required Login done; password is the token; host is your private domain

4. Push internal images

docker build -t myapp:1.0.0 .

docker tag myapp:1.0.0 "$REPO_HOST/docker-hosted/myapp:1.0.0"
docker push "$REPO_HOST/docker-hosted/myapp:1.0.0"
Enter fullscreen mode Exit fullscreen mode

Target must be HOSTED. Pushing to GROUP or PROXY yields denied.

Same three steps in CI:

echo "$REPO_TOKEN" | docker login "$REPO_HOST" \
  --username "$REPO_USER" --password-stdin
docker build -t "$REPO_HOST/docker-hosted/myapp:$VERSION" .
docker push "$REPO_HOST/docker-hosted/myapp:$VERSION"
Enter fullscreen mode Exit fullscreen mode

Pipeline stages: Jenkins build, test, publish. Rejected pushes: push denied.

5. Pull images

# Internal
docker pull "$REPO_HOST/docker-hosted/myapp:1.0.0"

# Public base images via PROXY (note library/)
docker pull "$REPO_HOST/dockerhub-proxy/library/nginx:1.27"

# Unified entry if you created GROUP
docker pull "$REPO_HOST/docker-group/myapp:1.0.0"
docker pull "$REPO_HOST/docker-group/library/nginx:1.27"
Enter fullscreen mode Exit fullscreen mode

Kubernetes

Nodes must resolve REPO_HOST. On HTTP registries, configure Docker / containerd on nodes to trust that host (same idea as insecure-registries).

kubectl create secret docker-registry repo-cred \
  --docker-server="$REPO_HOST" \
  --docker-username="$REPO_USER" \
  --docker-password="$REPO_TOKEN" \
  --namespace=default
Enter fullscreen mode Exit fullscreen mode
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      imagePullSecrets:
        - name: repo-cred
      containers:
        - name: myapp
          image: repo.example.com/docker-hosted/myapp:1.0.0
Enter fullscreen mode Exit fullscreen mode

6. Day-two ops (after push/pull works)

Tag cleanup — images grow fast. Keep the last N tags; drop *-dev / pr-* by name. Dry Run first—cleanup.

PROXY and rate limits — Hub anonymous pulls are about 100 / 6h. Put Hub credentials on dockerhub-proxy; pin versions in production; avoid latest.

Ingest checks — uploads get a light admission gate; deeper vuln/license work can use an external scanner or security product. Clear false positives before enabling block—admission gates and SBOM.

Error cheat sheet

Error Likely cause Fix
denied No Write, or push to GROUP/PROXY Fix ACL; HOSTED only; push denied
manifest unknown Wrong repo/image/tag; missing library/ Connection config and naming
Large pull timeout Proxy timeout / body limit Raise proxy_read_timeout and upload size
Login OK, push 401 Expired token or stale login docker login again

Field-level detail: Docker / OCI format manual.

FAQ

Can I push to a GROUP?

No. GROUP / PROXY are read-only; push only to HOSTED.

Should the image name include /v2?

No. The client adds the protocol path; putting it in the name breaks routing.

Is insecure-registries required on HTTP?

Yes. Without a certificate Docker defaults to HTTPS and usually fails with x509 or a handshake error. Remove it after you terminate real TLS.

Takeaways

  • Install and open the console → create HOSTED (required), PROXY, optional GROUP → issue a token.
  • After docker login, push only to HOSTED; pull from HOSTED, PROXY, or GROUP.
  • HTTP labs need insecure-registries; drop it on HTTPS; never hand-write /v2.
  • After the path works, set cleanup and PROXY credentials so disk and Hub limits do not stall builds.

Reference articles

Top comments (0)