Why does Kamal fail with pull access denied on deploy?
The server cannot log in to the registry that holds your image. Kamal pushes from your machine, where you are already authenticated, then asks the server to pull, where it is not. Check the registry block in config/deploy.yml names the right username, and that KAMAL_REGISTRY_PASSWORD is actually set in .kamal/secrets.
Confirm what the server is being told
bin/kamal secrets print
bin/kamal registry login
secrets print shows the names and whether they resolved, which is the fast way to spot a blank
value. registry login runs the authentication step on its own, so you get the registry's own error
message rather than a failed pull three steps later.
The usual causes, in order
The secret is not exported. .kamal/secrets is not committed, by design, so a fresh clone or a
new CI runner has no registry password at all. The file reads values from your environment or a
password manager; if the variable is unset, the secret is empty and the error is an authentication
failure rather than a missing-value error.
The token expired or was scoped wrong. A GitHub Container Registry token needs write:packages
to push and read:packages to pull. A token with only the write scope pushes successfully from your
laptop and then fails on the server, which is exactly the shape of this error.
The image name does not match the registry. For GHCR the image is ghcr.io/<owner>/<repo>, and
the owner is case sensitive in some clients. A mismatch here authenticates fine and then fails to
find the image, with a similar looking message.
Private registries and a fresh server
A server that has never pulled from this registry has no stored credentials. Kamal writes them
during kamal setup, so a server provisioned by hand, or rebuilt, needs setup run again rather than
just a deploy.
bin/kamal setup
That is also the moment to confirm the rest of the secrets resolve, because bin/kamal setup sends
them all. RAILS_MASTER_KEY is the one that turns a registry problem into a boot problem the moment
the pull starts working.
Rule out the network before you chase credentials
ssh your-server 'docker pull hello-world'
If that fails too, the problem is outbound network or DNS on the host, not your registry configuration, and no amount of token fixing will help.