You're authenticated, but as the wrong user or against the wrong repo path. The tag name must match the registry/repo you own — most denials are a missing namespace or username typo in the tag.
docker push myapp:latest defaults to docker.io/library/myapp — the official namespace you don't own. It must be docker push <username>/myapp:latest.
docker login with a personal account but pushing to an org repo you lack write access to. Check: docker system info | grep Username.
ECR requires the full registry path (aws_account.dkr.ecr.region.amazonaws.com/repo:tag) and the repo policy grants push. GCR/ghcr have their own path formats and token scopes.
docker system info | grep -i username ; docker image ls | grep <image>
docker tag myapp:latest <username>/myapp:latest && docker push <username>/myapp:latest
aws ecr get-login-password --region <r> | docker login --password-stdin <acct>.dkr.ecr.<r>.amazonaws.com && docker push <acct>.dkr.ecr.<r>.amazonaws.com/myrepo:tag
echo $GITHUB_TOKEN | docker login ghcr.io -u <user> --password-stdin && docker push ghcr.io/<owner>/myapp:tag
docker login can hold different creds per registry host simultaneously — check which one applies to the exact registry in your tag. Renamed Docker Hub repos: old local tags still reference the old path; retag before push.
Login proves identity; push needs the tag path to match a repo you (or your org) can write. Most common miss: pushing 'myapp' instead of '<username>/myapp' — the implicit library/ namespace is not yours.
Be a member with write access (org settings), log in with an account that has it, and tag with the org name as namespace: org/myapp:tag. Personal logins can't push by default.
A production-shaped compose stack: healthchecks, resource limits, log rotation. Never debug a boot race again.
Docker Production Starter — $19 →One-time. Yours to modify. Instant download from the NinjaOps template store.