Skip to content
Merged
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
81 changes: 11 additions & 70 deletions plugins/pass/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package pass

import (
"context"
_ "embed"
"errors"
"os"
"strings"
Expand Down Expand Up @@ -49,82 +50,22 @@ Examples:
{{.Example}}{{end}}
`

const rootExample = `
### Using keychain secrets in containers

Create a secret:

` + "```" + `console
$ docker pass set GH_TOKEN=123456789
` + "```" + `

Create a secret from STDIN:

` + "```" + `console
echo "my_val" | docker pass set GH_TOKEN
` + "```" + `

Run a container that uses the secret:

` + "```" + `console
$ docker run -e GH_TOKEN= -dt --name demo busybox
` + "```" + `

Inspect the secret from inside the container:

` + "```" + `console
$ docker exec demo sh -c 'echo $GH_TOKEN'
123456789
` + "```" + `

Explicitly assign a secret to a different environment variable:

` + "```" + `console
$ docker run -e GITHUB_TOKEN=se://GH_TOKEN -dt --name demo busybox
` + "```" + `

### Using keychain secrets in Compose

Store the secrets:

` + "```" + `console
$ docker pass set myapp/anthropic/api-key=sk-ant-...
$ docker pass set myapp/postgres/password=s3cr3t
` + "```" + `

` + "```" + `yaml
services:
api:
image: service1
environment:
- ANTHROPIC_API_KEY=se://myapp/anthropic/api-key
- POSTGRES_PASSWORD=se://myapp/postgres/password

worker:
image: service2
command: worker
environment:
- ANTHROPIC_API_KEY=se://myapp/anthropic/api-key

db:
image: postgres:17
environment:
- POSTGRES_PASSWORD=se://myapp/postgres/password
` + "```"
//go:embed examples.md
var rootExample string

// Root returns the root command for the docker-pass CLI plugin
func Root(ctx context.Context, s store.Store, info commands.VersionInfo) *cobra.Command {
cmd := &cobra.Command{
Use: "pass set|get|ls|rm|run",
Short: "Manage your local OS keychain secrets.",
Long: `Docker Pass is a helper for securely storing secrets in your local OS keychain and injecting them into containers when needed.
It uses platform-specific credential storage:

- Windows: Windows Credential Manager API
- macOS: Keychain services API
- Linux: org.freedesktop.secrets API (requires DBus + gnome-keyring or kdewallet)

Secrets can be injected into running containers at runtime using the se:// URI scheme.`,
Long: "Docker Pass is a helper for securely storing secrets in your local OS keychain and injecting them into containers when needed.\n" +
"It uses platform-specific credential storage:\n" +
"\n" +
" - Windows: Windows Credential Manager API\n" +
" - macOS: Keychain services API\n" +
" - Linux: `org.freedesktop.secrets` API (requires DBus + `gnome-keyring` or `kdewallet`)\n" +
"\n" +
"Secrets can be injected into running containers at runtime using the `se://` URI scheme.",
Example: strings.TrimSpace(rootExample),
SilenceUsage: true,
TraverseChildren: true,
Expand Down
15 changes: 6 additions & 9 deletions plugins/pass/commands/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package commands

import (
"context"
_ "embed"
"errors"
"fmt"
"io"
Expand All @@ -27,6 +28,9 @@ import (
"github.com/docker/secrets-engine/store"
)

//go:embed rm_example.md
var rmExample string

type rmOpts struct {
All bool
}
Expand All @@ -37,15 +41,8 @@ func RmCommand(kc store.Store) *cobra.Command {
Use: "rm name1 name2 ...",
Aliases: []string{"delete", "erase", "remove"},
Short: "Remove secrets from local keychain.",
Long: "Removes one or more named secrets from the local OS keychain.\nUse --all to remove every stored secret at once.",
Example: `### Remove a specific secret:
docker pass rm GH_TOKEN

### Remove multiple secrets:
docker pass rm GH_TOKEN NPM_TOKEN

### Remove all secrets:
docker pass rm --all`,
Long: "Removes one or more named secrets from the local OS keychain. Use `--all` to remove every stored secret at once.",
Example: strings.Trim(rmExample, "\n"),
RunE: func(cmd *cobra.Command, args []string) error {
idList, err := validateArgs(args, opts)
if err != nil {
Expand Down
17 changes: 17 additions & 0 deletions plugins/pass/commands/rm_example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### Remove a specific secret:

```console
$ docker pass rm GH_TOKEN
```

### Remove multiple secrets:

```console
$ docker pass rm GH_TOKEN NPM_TOKEN
```

### Remove all secrets:

```console
$ docker pass rm --all
```
36 changes: 13 additions & 23 deletions plugins/pass/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package commands

import (
"context"
_ "embed"
"errors"
"fmt"
"maps"
Expand Down Expand Up @@ -48,19 +49,8 @@ func (e *ExitCodeError) Error() string {
return fmt.Sprintf("child exited with code %d", e.Code)
}

const runExample = `
### Run a command with one secret in its environment:
SE_TOKEN=se://gh-token docker pass run -- gh repo list

### Multiple references:
DB_PASSWORD=se://myapp/postgres/password API_KEY=se://myapp/anthropic/api-key docker pass run -- ./my-binary

### Resolve references from a dotenv file:
docker pass run --env-file .env -- ./my-binary

### Multiple files (later overrides earlier; files override the process environment):
docker pass run --env-file .env --env-file .env.local -- ./my-binary
`
//go:embed run_example.md
var runExample string

type runOpts struct {
envFiles []string
Expand All @@ -70,16 +60,16 @@ func RunCommand() *cobra.Command {
opts := runOpts{}
cmd := &cobra.Command{
Use: "run -- CMD [ARGS...]",
Short: "Run a command with se:// environment references resolved.",
Long: `Scans the current environment (plus any --env-file inputs) for variables
whose value is exactly se://NAME. Each reference is resolved through the
secrets-engine daemon and the resolved value is passed to the child process.
The child inherits stdin, stdout, and stderr.

Requires the secrets-engine daemon (Docker Desktop) to be running.

If any reference cannot be resolved, the command fails before the child is
started and exits non-zero.`,
Short: "Run a command with `se://` environment references resolved.",
Long: "Scans the current environment (plus any `--env-file` inputs) for variables\n" +
"whose value is exactly `se://<ID|pattern>`. Each reference is resolved through the\n" +
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] se://<ID|pattern> in Long description misrepresents supported syntax

The updated Long description says:

"whose value is exactly se://<ID|pattern>"

This implies glob/wildcard patterns (e.g. se://myapp/*) are accepted. However, the underlying resolveRef function calls secrets.ParseID(name) first, which validates only [A-Za-z0-9._:-/] — the * wildcard character is explicitly excluded. Any se:// reference containing a wildcard is rejected before pattern resolution is attempted.

The original description said se://NAME, which accurately reflected the supported syntax. Consider reverting to se://<NAME> or se://<ID> to avoid misleading users who will get a cryptic error when they try se://myapp/*.

"secrets-engine daemon and the resolved value is passed to the child process.\n" +
"The child inherits stdin, stdout, and stderr.\n" +
"\n" +
"Requires the secrets-engine daemon (Docker Desktop) to be running.\n" +
"\n" +
"If any reference cannot be resolved, the command fails before the child is\n" +
"started and exits non-zero.",
Example: strings.Trim(runExample, "\n"),
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
23 changes: 23 additions & 0 deletions plugins/pass/commands/run_example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
### Run a command with one secret in its environment:

```console
$ SE_TOKEN=se://gh-token docker pass run -- gh repo list
```

### Multiple references:

```console
$ DB_PASSWORD=se://myapp/postgres/password API_KEY=se://myapp/anthropic/api-key docker pass run -- ./my-binary
```

### Resolve references from a dotenv file:

```console
$ docker pass run --env-file .env -- ./my-binary
```

### Multiple files (later overrides earlier; files override the process environment):

```console
$ docker pass run --env-file .env --env-file .env.local -- ./my-binary
```
40 changes: 13 additions & 27 deletions plugins/pass/commands/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package commands

import (
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
Expand All @@ -29,23 +30,8 @@ import (
"github.com/docker/secrets-engine/x/secrets"
)

const setExample = `
### Set a secret:
docker pass set POSTGRES_PASSWORD=my-secret-password

### Or pass the secret via STDIN:
echo my-secret-password > pwd.txt
cat pwd.txt | docker pass set POSTGRES_PASSWORD

### Set a secret with metadata:
docker pass set POSTGRES_PASSWORD=my-secret-password --metadata owner=alice --metadata expiry=2027-03-01

### Or pass a JSON payload with secret and metadata via STDIN:
echo '{"secret":"my-secret-password","metadata":{"owner":"alice"}}' | docker pass set POSTGRES_PASSWORD

### Overwrite an existing secret:
docker pass set POSTGRES_PASSWORD=new-secret-password --force
`
//go:embed set_example.md
var setExample string

type setOpts struct {
metadata []string // raw "key=value" strings from --metadata flag
Expand All @@ -63,16 +49,16 @@ func SetCommand(kc store.Store) *cobra.Command {
Use: "set id[=value]",
Aliases: []string{"store", "save"},
Short: "Set a secret",
Long: `Stores a secret in the local OS keychain. The secret value can be provided inline (NAME=VALUE) or piped via STDIN.

Behavior when a secret with the same id already exists is platform-dependent:
- macOS (Keychain): the command fails with a duplicate-item error.
- Linux (Secret Service) and Windows (Credential Manager): the existing
value is silently overwritten.

Pass --force to overwrite an existing secret. On Linux and Windows the
replacement is performed atomically. On macOS the Keychain API requires
a delete-then-add sequence.`,
Long: "Stores a secret in the local OS keychain. The secret value can be provided inline (`NAME=VALUE`) or piped via STDIN.\n" +
"\n" +
"Behavior when a secret with the same id already exists is platform-dependent:\n" +
" - macOS (Keychain): the command fails with a duplicate-item error.\n" +
" - Linux (Secret Service) and Windows (Credential Manager): the existing\n" +
" value is silently overwritten.\n" +
"\n" +
"Pass `--force` to overwrite an existing secret. On Linux and Windows the\n" +
"replacement is performed atomically. On macOS the Keychain API requires\n" +
"a delete-then-add sequence.",
Example: strings.Trim(setExample, "\n"),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down
30 changes: 30 additions & 0 deletions plugins/pass/commands/set_example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
### Set a secret:

```console
$ docker pass set POSTGRES_PASSWORD=my-secret-password
```

### Or pass the secret via STDIN:

```console
$ echo my-secret-password > pwd.txt
$ cat pwd.txt | docker pass set POSTGRES_PASSWORD
```

### Set a secret with metadata:

```console
$ docker pass set POSTGRES_PASSWORD=my-secret-password --metadata owner=alice --metadata expiry=2027-03-01
```

### Or pass a JSON payload with secret and metadata via STDIN:

```console
$ echo '{"secret":"my-secret-password","metadata":{"owner":"alice"}}' | docker pass set POSTGRES_PASSWORD
```

### Overwrite an existing secret:

```console
$ docker pass set POSTGRES_PASSWORD=new-secret-password --force
```
61 changes: 61 additions & 0 deletions plugins/pass/examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
### Using keychain secrets in containers

Create a secret:

```console
$ docker pass set GH_TOKEN=123456789
```

Create a secret from STDIN:

```console
echo "my_val" | docker pass set GH_TOKEN
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Inconsistent shell prompt prefix in console block

Every other console fenced block in examples.md uses a $ prompt prefix (lines 6, 19, 24–25, 30, 38–39), but the "Create a secret from STDIN" block on line 12 does not:

echo "my_val" | docker pass set GH_TOKEN

This makes the line look like output rather than a command to type, which will confuse users reading docker pass --help. It should be:

$ echo "my_val" | docker pass set GH_TOKEN

```

Run a container that uses the secret:

```console
$ docker run -e GH_TOKEN= -dt --name demo busybox
```

Inspect the secret from inside the container:

```console
$ docker exec demo sh -c 'echo $GH_TOKEN'
123456789
```

Explicitly assign a secret to a different environment variable:

```console
$ docker run -e GITHUB_TOKEN=se://GH_TOKEN -dt --name demo busybox
```

### Using keychain secrets in Compose

Store the secrets:

```console
$ docker pass set myapp/anthropic/api-key=sk-ant-...
$ docker pass set myapp/postgres/password=s3cr3t
```

```yaml
services:
api:
image: service1
environment:
- ANTHROPIC_API_KEY=se://myapp/anthropic/api-key
- POSTGRES_PASSWORD=se://myapp/postgres/password

worker:
image: service2
command: worker
environment:
- ANTHROPIC_API_KEY=se://myapp/anthropic/api-key

db:
image: postgres:17
environment:
- POSTGRES_PASSWORD=se://myapp/postgres/password
```
Loading