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(t.Context(), client.MustParsePattern("my-secret"))
42119if err != nil {
43120 log.Fatalf("failed fetching secrets: %v", err)
44121}
45- fmt.Println (resp.Value )
122+ // Since we used an exact match, only one secret should be present.
123+ if len(secrets) == 0 {
124+ log.Fatalf("hmm... seems like the plugin isn't returning an ErrNotFound")
125+ }
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,87 @@ package main
99180
100181import (
101182 "context"
183+ "log/slog"
102184
103185 "github.com/docker/secrets-engine/plugin"
104186)
105187
188+ type myLogger struct{}
189+
190+ func (m *myLogger) Errorf(format string, v ...any) {
191+ slog.Error(fmt.Sprintf(format, v...))
192+ }
193+
194+ func (m *myLogger) Printf(format string, v ...any) {
195+ slog.Info(fmt.Sprintf(format, v...))
196+ }
197+
198+ func (m *myLogger) Warnf(format string, v ...any) {
199+ slog.Warn(fmt.Sprintf(format, v...))
200+ }
201+
106202func 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)
203+ config := plugin.Config{
204+ Version: plugin.MustNewVersion("v0.0.1"),
205+ Pattern: plugin.MustParsePattern("myrealm/**"),
206+ // custom logger
207+ Logger: &myLogger{},
208+ }
209+ secrets := map[plugin.ID]string{
210+ plugin.MustParseID("myrealm/foo"): "bar",
114211 }
212+ p, err := plugin.New(&myPlugin{secrets: secrets}, config)
213+ if err != nil {
214+ panic(err)
215+ }
216+ // Run your plugin
217+ if err := p.Run(context.Background()); err != nil {
218+ panic(err)
219+ }
115220}
116221` ` `
117222
118- ### 3. Test and verify the plugin:
223+ # ## 3. Query secrets from your plugin:
119224
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:
225+ To verify your plugin works, run the binary and it should connect to the
226+ Secrets Engine.
124227
125- ``` console
126- $ curl --unix-socket ~ /Library/Caches/docker-secrets-engine/engine.sock \
228+ As a quick test we can retrieve secrets using `curl`, when running standalone
229+ the default socket is `daemon.sock` and with Docker Desktop it is `engine.sock`.
230+ Below we will query the Secrets Engine in standalone mode.
231+
232+ ` ` ` bash
233+ curl --unix-socket ~/Library/Caches/docker-secrets-engine/daemon.sock \
127234 -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"}
235+ -H "Content-Type: application/json" \
236+ -d '{"pattern": "myrealm/**"}'
237+ ` ` `
238+
239+ The value of a secret is always encoded into base64.
240+ When using Go's `json.Unmarshal` it will automatically convert it back into
241+ a slice of bytes `[]byte`.
242+
243+ To manually decode it, you can pipe the value into `base64 --decode -i`.
244+
245+ ` ` ` bash
246+ echo "<base64 string>" | base64 --decode -i
130247` ` `
248+
249+ # # Legal
250+
251+ _Brought to you courtesy of our legal counsel. For more context,
252+ see the [NOTICE](https://github.com/docker/secrets-engine/blob/main/NOTICE) document in this repo._
253+
254+ Use and transfer of Docker may be subject to certain restrictions by the
255+ United States and other governments.
256+
257+ It is your responsibility to ensure that your use and/or transfer does not
258+ violate applicable laws.
259+
260+ For more information, see https://www.bis.doc.gov
261+
262+ # # Licensing
263+
264+ docker/secrets-engine is licensed under the Apache License, Version 2.0. See
265+ [LICENSE](https://github.com/docker/secrets-engine/blob/main/LICENSE) for the full
266+ license text.
0 commit comments