Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions cmd/root/debug_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
// authInfo holds the parsed JWT authentication information.
type authInfo struct {
Token string `json:"token"`
Source string `json:"source,omitempty"`
Subject string `json:"subject,omitempty"`
Issuer string `json:"issuer,omitempty"`
IssuedAt time.Time `json:"issued_at,omitzero"`
Expand All @@ -30,7 +31,7 @@ func newDebugAuthCmd() *cobra.Command {

cmd := &cobra.Command{
Use: "auth",
Short: "Print Docker Desktop authentication information",
Short: "Print Docker authentication information",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) (commandErr error) {
ctx := cmd.Context()
Expand All @@ -41,21 +42,22 @@ func newDebugAuthCmd() *cobra.Command {

w := cmd.OutOrStdout()

token := desktop.GetToken(ctx)
token, source := desktop.GetTokenWithSource(ctx)
if token == "" {
if jsonOutput {
return json.NewEncoder(w).Encode(map[string]string{
"error": "no token found (is Docker Desktop running and are you logged in?)",
"error": "no token found (is Docker Desktop running, or are you logged in with `docker login`?)",
})
}
fmt.Fprintln(w, "No token found. Is Docker Desktop running and are you logged in?")
fmt.Fprintln(w, "No token found. Is Docker Desktop running, or are you logged in with `docker login`?")
return nil
}

info, err := parseAuthInfo(token)
if err != nil {
return fmt.Errorf("failed to parse JWT: %w", err)
}
info.Source = string(source)

userInfo := desktop.GetUserInfo(ctx)
info.Username = userInfo.Username
Expand Down Expand Up @@ -112,6 +114,9 @@ func printAuthInfoText(w io.Writer, info *authInfo) {
fmt.Fprintf(w, "Token: %s...%s\n", info.Token[:previewLen], info.Token[len(info.Token)-previewLen:])
}

if info.Source != "" {
fmt.Fprintf(w, "Source: %s\n", info.Source)
}
if info.Username != "" {
fmt.Fprintf(w, "Username: %s\n", info.Username)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/root/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (f *doctorFlags) buildReport(ctx context.Context, agentRef string) (*doctor
if _, ok := findSource(ctx, sources, environment.DockerDesktopTokenEnv); !ok {
autoStatus.Usable = false
autoIssues = append(autoIssues,
"the models gateway requires Docker Desktop sign-in and no DOCKER_TOKEN was found; sign in to Docker Desktop (check with `docker agent debug auth`)")
"the models gateway requires a Docker sign-in and no DOCKER_TOKEN was found; sign in to Docker Desktop or run `docker login` (check with `docker agent debug auth`)")
}
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/root/doctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,8 @@ func TestDoctorCommand_DockerGatewayNeedsSignIn(t *testing.T) {
withDoctorTestEnv(nil, nil, dmr.ErrNotInstalled))

require.Error(t, err)
assert.Contains(t, output, "requires Docker Desktop sign-in")
assert.Contains(t, output, "requires a Docker sign-in")
assert.Contains(t, output, "docker login")

output, err = executeDoctor(t, []string{"--models-gateway", "https://api.docker.com/gateway"},
withDoctorTestEnv(map[string]string{"DOCKER_TOKEN": "jwt"}, nil, dmr.ErrNotInstalled))
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration/overview/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ API keys and secrets are read from environment variables — never stored in con
| `DOCKER_AGENT_MODELS_GATEWAY` | Route model traffic through a gateway. Equivalent to the `--models-gateway` flag. |
| `DOCKER_AGENT_HIDE_TELEMETRY_BANNER`| Set to `1` to suppress the first-run telemetry notice. |
| `DOCKER_AGENT_AUTO_UPDATE` | Set to a truthy value (`1`, `true`, `yes`, `on`) to let standalone release binaries self-update before running. See [Optional Self-Updates](../../getting-started/installation/index.md#optional-self-updates). |
| `DOCKER_AGENT_NO_TOKEN_EXCHANGE` | Set to `1` to stop Docker Agent from exchanging the access token stored by `docker login` for a Docker token. See [Docker authentication](../../guides/secrets/index.md#docker-authentication). |
| `DOCKER_AGENT_HUB_LOGIN_URL` | Point the token exchange at a Docker staging environment. Ignored unless it is an HTTPS `docker.com` URL. |

> [!NOTE]
> **Legacy `CAGENT_*` aliases**
Expand Down
6 changes: 4 additions & 2 deletions docs/features/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ $ docker agent debug <subcommand> [flags]
| `toolsets <agent-file>` | List every toolset each agent in the config exposes, with each tool's name and description. |
| `skills <agent-file>` | List the skills discovered for each agent, marking forked skills. |
| `title <agent-file> <question>` | Generate a session title for `<question>` using the same title-generation path the TUI uses (including any configured `title_model`), without starting a session. See [Session Titles](../sessions/index.md#session-titles). |
| `auth` | Print parsed Docker Desktop authentication info from the locally stored JWT (subject, issuer, expiry, username/email). Add `--json` for machine-readable output. |
| `auth` | Print parsed Docker authentication info from the token in use (source, subject, issuer, expiry, username/email). Add `--json` for machine-readable output. |
| `oauth list` | List stored MCP OAuth tokens (resource, scope, expiry, redacted access token). Add `--json` for machine-readable output. |
| `oauth remove <resource-url>` | Remove a stored MCP OAuth token. |
| `oauth login <agent-file> <mcp-name>` | Perform an interactive OAuth login for a remote MCP server declared in the config, by its name or URL. See [Remote MCP Servers](../remote-mcp/index.md). |
Expand All @@ -693,7 +693,9 @@ $ docker agent debug oauth login agent.yaml github
> [!WARNING]
> **`debug auth --json` prints the full bearer token**
>
> The text output of `debug auth` truncates the token to a short preview, but `--json` includes the complete, unredacted JWT in its `token` field. Never paste `debug auth --json` output into logs, issue trackers, or bug reports — anyone with that token can act as you against Docker Desktop's backend. Use the plain-text output (or redact the `token` field yourself) when sharing diagnostic output.
> The text output of `debug auth` truncates the token to a short preview, but `--json` includes the complete, unredacted JWT in its `token` field. Never paste `debug auth --json` output into logs, issue trackers, or bug reports — anyone with that token can act as you against Docker. Use the plain-text output (or redact the `token` field yourself) when sharing diagnostic output.

The `Source` field says where the token came from: `docker desktop`, or `minted from the stored access token` when it was obtained by exchanging the access token `docker login` stored. See [Docker authentication](../../guides/secrets/index.md#docker-authentication).

The `config`, `toolsets`, `skills`, and `title` subcommands also accept [runtime configuration flags](#runtime-configuration-flags) (`--working-dir`, `--models-gateway`, …); `title` additionally accepts `--model` to override the model used to resolve the config before generating the title.

Expand Down
8 changes: 8 additions & 0 deletions docs/guides/secrets/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ The command is invoked with the variable name appended as the final argument, an

On machines where Docker Desktop is installed, Docker Agent queries Docker Desktop's backend for secrets stored against your signed-in Docker account. This is transparent — no extra configuration — and it is how signed-in Docker users get provider API keys without setting any environment variables.

## Docker Authentication

Routing model traffic through the [Docker models gateway](../../configuration/models/index.md) needs a Docker token. Docker Desktop hands out one that is valid for 15 minutes and cannot be renewed by Docker Agent, so when Desktop has nothing usable to offer — it is signed out, not running, or its own refresh is stuck — Docker Agent exchanges the long-lived access token that `docker login` left in your credential store for a fresh Docker token, the same exchange `docker login` itself performs. Signing in with `docker login` is therefore enough; Docker Desktop is not required.

Only Docker access tokens are exchanged — the `dckr_…` secrets `docker login` stores — never an account password, and the exchange goes to Docker Hub over HTTPS. The resulting bearer token is cached in a private file under Docker Agent's cache directory so sibling processes reuse it instead of minting their own, and it stops being used within seconds of a `docker logout` or an account switch. Run `docker agent debug auth` to see which token is in use and where it came from.

Set `DOCKER_AGENT_NO_TOKEN_EXCHANGE=1` to opt out: Docker Agent then relies on Docker Desktop alone.

## 1Password References

Any secret value resolved through the chain above can be a **1Password secret reference** instead of the literal secret. If the value starts with `op://`, Docker Agent resolves it by invoking the [1Password CLI](https://developer.1password.com/docs/cli/) (`op read <reference>`) and uses the result.
Expand Down
Loading
Loading