## Confirm what the server is being told

```bash
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.

```bash
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

```bash
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.
