Secrets Engine and docker pass are bundled with Docker Desktop.
Runtime secret injection is now available in Docker CE as an experimental,
early-access feature. It requires Docker Engine (dockerd) 29.2.0 or
higher.
Download the latest packages for your Linux distribution from the releases, then install them:
# Replace with the files you downloaded (matching your distro and arch).
sudo apt install ./DockerSecretsEngine-linux-amd64-ubuntu2404.deb \
./DockerSecretsEnginePlugins-linux-ubuntu2404.deb
systemctl --user daemon-reload
systemctl --user enable --now docker-secrets-engine.serviceRecommended:
dbusβ required for the keyring backends.gnome-keyringorkwalletβ secret storage backend.
systemctl --user disable --now docker-secrets-engine.service
sudo apt remove docker-secrets-engine-plugins docker-secrets-engineWarning
Docker CE support is experimental and may change between releases. Do not rely on it for production workloads yet. Also see known limitations.
Secrets Engine lets you reference secrets in docker run / docker compose
and have Docker resolve and inject the real values at runtime.
Key idea: you pass a pointer, not the secret.
- In your config (CLI flags / Compose files), you use a
se://reference likese://foo. - When the container starts, Docker asks Secrets Engine to resolve that reference and injects the secret into the container.
- The secret value is sourced from a provider, such as
docker pass, which stores secrets securely in your local OS keychain (or from a custom provider plugin).
This means you donβt need:
- host environment variables containing secret values
- plaintext secret files on disk (such as
.envfiles) - secret literals embedded in
compose.yaml
Store the secret in your OS keychain:
docker pass set foo=barRun a container using a secret reference (the value se://foo is not the secret itself):
docker run --rm -e foo=se://foo busybox sh -c 'echo "$foo"'Compose example:
services:
app:
image: your/image
environment:
API_TOKEN: se://fooSecrets Engine supports realms: a simple namespacing scheme that helps to organize secrets by purpose, environment, application, or target system, and then retrieve them using glob-style patterns.
A realm is a prefix in the secret key. For example:
docker/auth/hub/mysecretdocker/auth/ghcr/tokendocker/db/prod/password
Because the realm is part of the key, you can query or operate on groups of secrets using patterns. For example, to target all Docker auth-related secrets:
docker/auth/**
This makes it easy to:
- keep related secrets grouped together
- separate environments (e.g.
prod/,staging/,dev/) - scope listing/lookup operations to a subset of secrets without knowing every key ahead of time
docker/
auth/
hub/
mysecret
ghcr/
token
db/
prod/
password
Tip
Treat realms like paths - predictable structure makes automation and access control much easier.
Note
Missing a plugin? Help us pick the next provider β vote π for your favorite (or request one) on the plugin backends epic.
Use the client module in your project:
go get github.com/docker/secrets-engine/clientUse the client to fetch a secret:
c, err := client.New()
if err != nil {
log.Fatalf("failed to create secrets engine client: %v", err)
}
// Fetch a secret from the engine
// We are using an exact match here, so only one or zero results will return.
secrets, err := c.GetSecrets(context.Background(), client.MustParsePattern("my-secret"))
if errors.Is(err, client.ErrSecretNotFound) {
log.Fatalf("no secret found")
}
// fallback to generic error
if err != nil {
log.Fatalf("failed fetching secrets: %v", err)
}
fmt.Println(secrets[0].Value)Use the plugin module in your project:
go get github.com/docker/secrets-engine/pluginA plugin needs to implement the Plugin interface:
var _ plugin.Plugin = &myPlugin{}
type myPlugin struct {
m sync.Mutex
secrets map[plugin.ID]string
}
func (p *myPlugin) GetSecrets(_ context.Context, pattern plugin.Pattern) ([]plugin.Envelope, error) {
p.m.Lock()
defer p.m.Unlock()
var result []plugin.Envelope
for id, value := range p.secrets {
if pattern.Match(id) {
result = append(result, plugin.Envelope{
ID: id,
Value: []byte(value),
CreatedAt: time.Now(),
})
}
}
return result, nil
}
func (p *myPlugin) Run(ctx context.Context) error {
// add long-running tasks here
// for example, OAuth tokens can be refreshed here.
<-ctx.Done()
return nil
}Create a Go binary that use your plugin interface implementation and runs it through the plugin SDK:
package main
import (
"context"
"fmt"
"log/slog"
"github.com/docker/secrets-engine/plugin"
)
type myLogger struct{}
func (m *myLogger) Errorf(format string, v ...any) {
slog.Error(fmt.Sprintf(format, v...))
}
func (m *myLogger) Printf(format string, v ...any) {
slog.Info(fmt.Sprintf(format, v...))
}
func (m *myLogger) Warnf(format string, v ...any) {
slog.Warn(fmt.Sprintf(format, v...))
}
func main() {
config := plugin.Config{
Version: plugin.MustNewVersion("v0.0.1"),
Pattern: plugin.MustParsePattern("myrealm/**"),
// custom logger
Logger: &myLogger{},
}
secrets := map[plugin.ID]string{
plugin.MustParseID("myrealm/foo"): "bar",
}
p, err := plugin.New(&myPlugin{secrets: secrets}, config)
if err != nil {
panic(err)
}
// Run your plugin
if err := p.Run(context.Background()); err != nil {
panic(err)
}
}To verify your plugin works, run the binary and it should connect to the Secrets Engine.
As a quick test we can retrieve secrets using curl, when running standalone
the default socket is daemon.sock and with Docker Desktop it is engine.sock.
Below we will query the Secrets Engine in standalone mode.
curl --unix-socket ~/Library/Caches/docker-secrets-engine/daemon.sock \
-X POST http://localhost/resolver.v1.ResolverService/GetSecrets \
-H "Content-Type: application/json" \
-d '{"pattern": "myrealm/**"}'The value of a secret is always encoded into base64.
When using Go's json.Unmarshal it will automatically convert it back into
a slice of bytes []byte.
To manually decode it, you can pipe the value into base64, using the
flags appropriate for your platform:
# macOS / BSD
echo "<base64 string>" | base64 -D
# GNU/Linux (coreutils)
echo "<base64 string>" | base64 --decode
# or
echo "<base64 string>" | base64 -dThese apply to the experimental Docker CE integration described above. We are actively working to address them.
- No multi-user support. A single Docker Engine is shared by every user on the host, but Secrets Engine runs as a per-user daemon. When multiple users are logged in and using the same engine in parallel, the engine cannot reliably route a resolution request to the right user's daemon.
- Requires a keyring backend. The daemon depends on D-Bus together with a Secret Service provider (GNOME Keyring or KWallet). On hosts where these are missing β typically headless or server installs β the daemon currently crashes instead of degrading gracefully. We are working on a fix; in the meantime, the workaround is to install and set up D-Bus and either GNOME Keyring or KWallet.
- No automatic restart after a
dockerdrestart. When the Docker Engine is restarted, the Secrets Engine daemon must be restarted manually (systemctl --user restart docker-secrets-engine) for injection to keep working.
Brought to you courtesy of our legal counsel. For more context, see the NOTICE document in this repo.
Use and transfer of Docker may be subject to certain restrictions by the United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not violate applicable laws.
For more information, see https://www.bis.doc.gov
docker/secrets-engine is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.