February 8, 2017·5 min read·engineeringdockersecretscomposeswarm

Docker stack files gain explicit secret dependencies

Docker 1.13.1 connects Compose secret declarations to Swarm objects, with separate handling for stack-owned and externally managed secrets.

An application image should be reusable without carrying the credentials of the environment that runs it. Docker 1.13.1 makes that separation easier to describe by adding secrets support to Compose-based stack deployment. The application file can now name a dependency that the cluster supplies separately from the image. This closes a gap between the service API and the application description accepted by the deployment command.

The release notes for 8 February attribute the change to pull request 30144. Secret management itself arrived in 1.13.0. The patch adds the connection to docker stack deploy, so describing this as the first Docker release with secrets would miss the actual change. A feature available through individual service operations becomes part of the stack workflow.

I prefer an image that describes the program independently of its deployment credentials. Docker's image model is useful precisely because the same artifact can move between machines. Attaching secrets through a deployment description preserves that property without pretending that every runtime input belongs in the build.

The client reads a file and creates an object

The Compose conversion code at the release tag shows how the feature works. For each non-external secret declaration, the client reads the specified file and constructs a Swarm SecretSpec. The bytes become its data. The code adds a stack namespace to the name and a stack label to the object's metadata.

This is an important distinction between describing the dependency and supplying its value. The Compose file can identify a file without containing the file's contents. The deployment client still needs access to that file when it performs the conversion. Moving credentials out of an image therefore changes where the deployment obtains them, while leaving a concrete input that must exist at deployment time.

Namespacing also affects how people reason about the resulting objects. The conversion joins the stack name and the declared secret name with an underscore. A secret declared within one stack consequently gets an identity associated with that stack. Two application descriptions can use the same short internal name without that spelling necessarily identifying one shared cluster object.

The stack label uses com.docker.stack.namespace. That label gives the resource an association that software can inspect, instead of requiring it to infer ownership entirely from a naming convention. Both the name and the label belong to the conversion step, before the client creates or updates services.

External secrets keep a separate owner

The same conversion function skips secrets marked external. That behavior establishes a different ownership model. The stack refers to a dependency that another operation has already created, rather than creating it by reading the declared file. A shared secret's lifecycle can therefore remain independent of an individual application deployment.

Docker's versioned secret creation documentation describes creating a secret from a file or standard input. The operation must target a manager node. A file-based command has this form:

docker secret create application-password ./password.txt

The command creates a named object and returns its identifier. The documentation also shows listing and inspecting secrets, including labels. Its inspection example returns a null data field, which distinguishes inspecting object metadata from reading back the submitted bytes through that output. The sample does not provide a mechanism for recovering a lost source file.

The external distinction is useful when the application owner and credential owner have different responsibilities. The application can declare what it needs, while another process creates the named dependency. A stack-owned file input remains useful for a different deployment arrangement. The feature supports those different relationships without forcing secret values into image construction.

Deployment orders the dependencies

The stack deployment implementation loads the Compose configuration and checks that the target daemon is a Swarm manager. It handles networks before secrets, then converts and deploys services. Secrets therefore exist before service deployment attempts to use their references.

For each stack-owned secret, the client looks for an existing object matching the name. No match leads to creation. An existing match leads to a secret update call with the object's version. An ambiguous name returns an error. These branches explain why repeated deployment is more involved than blindly creating a new secret every time the command runs.

The update call also shows that reusing a name involves server-side validation. A deployment file is not proof that arbitrary new secret contents can replace an existing object's contents. The code explicitly comments that its update attempt checks whether local data has changed. The client submits the specification and handles the response before it proceeds with service deployment.

This sequence makes partial progress possible. Networks may already exist when secret processing encounters an unreadable file or an API error. Reading the implementation helps explain the resulting cluster state without assuming that stack deployment wraps every resource change in one transaction. The order is explicit, and each error can stop the remaining work.

Plugin upgrades address another persistent dependency

The same patch introduces docker plugin upgrade. Although unrelated to secret contents, it solves a similar lifecycle problem for objects that applications depend on. An installed plugin needs a path to new code that preserves existing references to the plugin.

The 1.13.1 command documentation says that the plugin must be disabled before upgrading. With no remote image argument, Docker pulls the current image again and uses its updated version. Supplying a remote image names the replacement explicitly. Existing references continue to work, which is the practical reason to upgrade an object instead of deleting and recreating it under a new identity.

Its volume-plugin example also makes the interruption visible. Volumes provided by the disabled plugin disappear from the volume listing during the operation, then reappear after enablement. Preserving references does not imply continuous availability of the implementation. The documented sequence includes disabling, upgrading and enabling because code replacement and service availability are different properties.

I find these lifecycle details more useful than the patch version alone suggests. Stack secrets reduce the amount of environment-specific material that has to enter an image, while plugin upgrades preserve an identity around a replaceable implementation. In both cases the application depends on a named object outside its own filesystem. Docker 1.13.1 gives those dependencies more explicit operations and makes their ownership visible in the deployment process.