44[ ![ lint] ( https://github.com/docker/secrets-engine/actions/workflows/lint.yml/badge.svg?branch=main )] ( https://github.com/docker/secrets-engine/actions/workflows/lint.yml )
55[ ![ License] ( https://img.shields.io/badge/license-Apache%202.0-purple )] ( https://github.com/docker/secrets-engine/blob/main/LICENSE )
66
7- ## Quickstart
7+ Secrets Engine and [ docker pass] ( https://docs.docker.com/reference/cli/docker/pass/ )
8+ are bundled with [ Docker Desktop] ( https://docs.docker.com/desktop/ ) .
9+ A standalone version can also be installed from the [ releases] ( https://github.com/docker/secrets-engine/releases ) .
810
9- Secrets Engine and [ docker pass] ( https://docs.docker.com/reference/cli/docker/pass/ ) are bundled with [ Docker Desktop] ( https://docs.docker.com/desktop/ ) .
11+ > [ !NOTE]
12+ > Secret injection in Docker CE is on our roadmap.
1013
11- Let's store a secret using ` docker pass ` in the OS Keychain and then inject it
12- into a running container using Secrets Engine.
14+ ## Runtime secret injection (no plaintext in your CLI or Compose)
1315
14- ``` console
15- # Store ` Foo` in the OS Keychain
16- $ docker pass set Foo=bar
17- # Tell docker to use the Secrets Engine using the ` se://` URI on an environment variable
18- $ docker run --rm -e Foo=se://Foo busybox /bin/sh -c " echo \$ {Foo}"
19- $ bar
16+ Secrets Engine lets you ** reference** secrets in ` docker run ` / ` docker compose `
17+ and have Docker ** resolve and inject** the real values _ at runtime_ .
18+
19+ ** Key idea:** you pass a _ pointer_ , not the secret.
20+
21+ - In your config (CLI flags / Compose files), you use a ` se:// ` reference like ` se://foo ` .
22+ - When the container starts, Docker asks Secrets Engine to resolve that reference
23+ and injects the secret into the container.
24+ - The secret value is sourced from a provider, such as ** ` docker pass ` ** , which
25+ stores secrets securely in your ** local OS keychain** (or from a custom provider plugin).
26+
27+ This means you don’t need:
28+
29+ - host environment variables containing secret values
30+ - plaintext secret files on disk (such as ` .env ` files)
31+ - secret literals embedded in ` compose.yaml `
32+
33+ ### Example: store once, use everywhere
34+
35+ Store the secret in your OS keychain:
36+
37+ ``` bash
38+ docker pass set foo=bar
39+ ```
40+
41+ Run a container using a secret reference (the value se://foo is not the secret itself):
42+
43+ ``` bash
44+ docker run --rm -e foo=se://foo busybox sh -c ' echo "$foo"'
45+ ```
46+
47+ Compose example:
48+
49+ ``` yaml
50+ services :
51+ app :
52+ image : your/image
53+ environment :
54+ API_TOKEN : se://foo
2055` ` `
2156
57+ ### Realms (namespace + pattern matching)
58+
59+ Secrets Engine supports **realms**: a simple namespacing scheme that helps to
60+ organize secrets by purpose, environment, application, or target system, and then retrieve
61+ them using glob-style patterns.
62+
63+ A realm is a prefix in the secret key. For example:
64+
65+ - ` docker/auth/hub/mysecret`
66+ - ` docker/auth/ghcr/token`
67+ - ` docker/db/prod/password`
68+
69+ Because the realm is part of the key, you can query or operate on groups of
70+ secrets using patterns. For example, to target _all_ Docker auth-related secrets :
71+
72+ - ` docker/auth/**`
73+
74+ This makes it easy to :
75+
76+ - keep related secrets grouped together
77+ - separate environments (e.g. `prod/`, `staging/`, `dev/`)
78+ - scope listing/lookup operations to a subset of secrets without knowing every
79+ key ahead of time
80+
81+ # ### Example layout
82+
83+ ` ` ` text
84+ docker/
85+ auth/
86+ hub/
87+ mysecret
88+ ghcr/
89+ token
90+ db/
91+ prod/
92+ password
93+ ` ` `
94+
95+ > [!TIP]
96+ > Treat realms like paths - predictable structure makes automation and access control much easier.
97+
2298# Developer Guides
2399
24100# # How to query secrets
@@ -38,11 +114,16 @@ if err != nil {
38114}
39115
40116// Fetch a secret from the engine
41- resp , err := c.GetSecrets (t.Context (), client.MustParsePattern (" my-secret" ))
117+ // We are using an exact match here, so only one or zero results will return.
118+ secrets, err := c.GetSecrets(context.Background(), client.MustParsePattern("my-secret"))
119+ if errors.Is(err, client.ErrSecretNotFound) {
120+ log.Fatalf("no secret found")
121+ }
122+ // fallback to generic error
42123if err != nil {
43124 log.Fatalf("failed fetching secrets: %v", err)
44125}
45- fmt.Println (resp .Value )
126+ fmt.Println(secrets[0] .Value)
46127` ` `
47128
48129# # How to create a plugin
@@ -62,17 +143,17 @@ var _ plugin.Plugin = &myPlugin{}
62143
63144type myPlugin struct {
64145 m sync.Mutex
65- secrets map [secrets .ID ]string
146+ secrets map[plugin .ID]string
66147}
67148
68- func (p *myPlugin ) GetSecret (_ context .Context , request secrets . Request ) ([]secrets .Envelope , error ) {
149+ func (p *myPlugin) GetSecrets (_ context.Context, pattern plugin.Pattern ) ([]plugin .Envelope, error) {
69150 p.m.Lock()
70151 defer p.m.Unlock()
71152
72- var result []secrets .Envelope
153+ var result []plugin .Envelope
73154 for id, value := range p.secrets {
74- if request. Pattern .Match (id) {
75- result = append (result, secrets .Envelope {
155+ if pattern .Match(id) {
156+ result = append(result, plugin .Envelope{
76157 ID: id,
77158 Value: []byte(value),
78159 CreatedAt: time.Now(),
@@ -82,11 +163,11 @@ func (p *myPlugin) GetSecret(_ context.Context, request secrets.Request) ([]secr
82163 return result, nil
83164}
84165
85- func (p *myPlugin ) Config () plugin . Config {
86- return plugin. Config {
87- Version: " v0.0.1 " ,
88- Pattern: " * " ,
89- }
166+ func (p *myPlugin) Run(ctx context.Context) error {
167+ // add long-running tasks here
168+ // for example, OAuth tokens can be refreshed here.
169+ <-ctx.Done()
170+ return nil
90171}
91172` ` `
92173
@@ -99,32 +180,95 @@ package main
99180
100181import (
101182 "context"
183+ "fmt"
184+ "log/slog"
102185
103186 "github.com/docker/secrets-engine/plugin"
104187)
105188
189+ type myLogger struct{}
190+
191+ func (m *myLogger) Errorf(format string, v ...any) {
192+ slog.Error(fmt.Sprintf(format, v...))
193+ }
194+
195+ func (m *myLogger) Printf(format string, v ...any) {
196+ slog.Info(fmt.Sprintf(format, v...))
197+ }
198+
199+ func (m *myLogger) Warnf(format string, v ...any) {
200+ slog.Warn(fmt.Sprintf(format, v...))
201+ }
202+
106203func main() {
107- p , err := plugin.New (&myPlugin{secrets: map [secrets.ID ]string {" foo" : " bar" }})
108- if err != nil {
109- panic (err)
110- }
111- // Run your plugin
112- if err := p.Run (context.Background ()); err != nil {
113- panic (err)
204+ config := plugin.Config{
205+ Version: plugin.MustNewVersion("v0.0.1"),
206+ Pattern: plugin.MustParsePattern("myrealm/**"),
207+ // custom logger
208+ Logger: &myLogger{},
209+ }
210+ secrets := map[plugin.ID]string{
211+ plugin.MustParseID("myrealm/foo"): "bar",
114212 }
213+ p, err := plugin.New(&myPlugin{secrets: secrets}, config)
214+ if err != nil {
215+ panic(err)
216+ }
217+ // Run your plugin
218+ if err := p.Run(context.Background()); err != nil {
219+ panic(err)
220+ }
115221}
116222` ` `
117223
118- ### 3. Test and verify the plugin:
224+ # ## 3. Query secrets from your plugin:
225+
226+ To verify your plugin works, run the binary and it should connect to the
227+ Secrets Engine.
119228
120- The secrets engine is integrated with Docker Desktop.
121- To verify your plugin works, run the binary.
122- Using the SDK it will automatically connect to the secrets engine in Docker Desktop.
123- Then, you can query secrets, e.g. using curl:
229+ As a quick test we can retrieve secrets using `curl`, when running standalone
230+ the default socket is `daemon.sock` and with Docker Desktop it is `engine.sock`.
231+ Below we will query the Secrets Engine in standalone mode.
124232
125- ``` console
126- $ curl --unix-socket ~ /Library/Caches/docker-secrets-engine/engine .sock \
233+ ` ` ` bash
234+ curl --unix-socket ~/Library/Caches/docker-secrets-engine/daemon .sock \
127235 -X POST http://localhost/resolver.v1.ResolverService/GetSecrets \
128- -H "Content-Type: application/json" -d '{"pattern": "foo"}'
129- {"id":"foo","value":"bar","provider":"docker-pass","version":"","error":"","createdAt":"0001-01-01T00:00:00Z","resolvedAt":"2025-08-12T08:25:06.166714Z","expiresAt":"0001-01-01T00:00:00Z"}
236+ -H "Content-Type: application/json" \
237+ -d '{"pattern": "myrealm/**"}'
130238` ` `
239+
240+ The value of a secret is always encoded into base64.
241+ When using Go's `json.Unmarshal` it will automatically convert it back into
242+ a slice of bytes `[]byte`.
243+
244+ To manually decode it, you can pipe the value into `base64`, using the
245+ flags appropriate for your platform :
246+
247+ ` ` ` bash
248+ # macOS / BSD
249+ echo "<base64 string>" | base64 -D
250+
251+ # GNU/Linux (coreutils)
252+ echo "<base64 string>" | base64 --decode
253+ # or
254+ echo "<base64 string>" | base64 -d
255+ ` ` `
256+
257+ # # Legal
258+
259+ _Brought to you courtesy of our legal counsel. For more context,
260+ see the [NOTICE](https://github.com/docker/secrets-engine/blob/main/NOTICE) document in this repo._
261+
262+ Use and transfer of Docker may be subject to certain restrictions by the
263+ United States and other governments.
264+
265+ It is your responsibility to ensure that your use and/or transfer does not
266+ violate applicable laws.
267+
268+ For more information, see https://www.bis.doc.gov
269+
270+ # # Licensing
271+
272+ docker/secrets-engine is licensed under the Apache License, Version 2.0. See
273+ [LICENSE](https://github.com/docker/secrets-engine/blob/main/LICENSE) for the full
274+ license text.
0 commit comments