April 7, 2020·6 min read·engineeringdockercomposestandards

Compose gets a specification independent of its implementation

Opening the Compose specification creates a place to define what an application file means across implementations, including where platform support can differ.

A file format becomes difficult to share across implementations when one implementation is the effective definition of its behavior. Docker's 7 April announcement addresses that problem for Compose. The company has opened a community to develop the Compose Specification, with public governance and participation from AWS, Microsoft and others. Docker says the earlier format had no published specification independent of its implementation and the platforms on which that implementation ran.

This is a change in how the application model can develop. It does not mean every platform already executes every Compose attribute. The announcement describes work to support platforms including Kubernetes and Amazon ECS, and presents foundation submission as an intention. Both points matter when distinguishing what has opened today from what may follow through implementation and governance work.

I regard Docker's repeatable environment description as a large part of its value. An independent Compose specification extends that concern to the relationship between services. The useful outcome is a document that lets an engineer reason about an application's requirements without first treating the behavior of one executable as the complete definition of the format.

The model separates services from their instances

The historical draft defines a service as an application component implemented by running the same image and configuration one or more times. A service therefore describes a repeated unit of computation. The individual container is an instance of that description. This distinction allows an application definition to remain meaningful when a platform runs more than one instance.

Networks express communication between services. Volumes express persistent filesystem data. The draft also distinguishes configuration from sensitive configuration through configs and secrets. Although each can appear to a container as mounted files, the platform resources that supply them have different purposes. Treating them as separate concepts gives implementations information that a generic file mount alone would not provide.

This is a useful level at which to describe application intent. An application can require a persistent data location without embedding every detail of how a particular platform provisions it. However, someone still has to map that requirement to platform resources. A specification can define the requirement and the vocabulary for its options. It cannot make the underlying storage implementation identical everywhere.

The draft calls an individual deployment a project. Its name groups resources and separates deployments of the same application with different parameters. Implementations creating resources must prefix their names by project and set the com.docker.compose.project label. That rule makes deployment identity part of the application model, rather than leaving grouping entirely to a user interface or naming convention outside the specification.

Optional support is part of the contract

The draft explicitly makes build support optional. A platform may be able to execute images without being able to build them from source. Its services may therefore use an image reference while another implementation supports a build description for local development. The main specification says a file remains valid when the implementation does not support the build section and ignores it.

The separate build document describes the supported case. A short build value identifies a context directory containing a Dockerfile. Relative contexts resolve from the Compose file's directory. A relative alternate Dockerfile path resolves from the context. The draft asks implementations to warn about absolute paths because those paths restrict portability.

For example, this illustrative fragment uses the draft's existing version syntax and a relative context. It assumes a web directory containing a Dockerfile next to the Compose file.

version: "3"
services:
  web:
    build: ./web

The fragment expresses how a supporting implementation can create the service image. It does not establish that a deployment platform will perform a build. An implementation with no build support would need an appropriate image reference to execute this service. That is an immediate practical consequence of the optional model, even though the document accepts the build section syntactically.

I prefer an explicit optional capability to an undocumented assumption that every consumer performs the same work. It makes the missing agreement visible. For an application author, the important distinction is between a valid description and a description containing everything a particular platform needs to execute the application.

Deployment has its own requirements

The deployment draft is optional too. It adds runtime requirements through a service's deploy section. The document distinguishes replicated services from global services, and defines constraints that a selected node must satisfy separately from preferences it should satisfy. These distinctions describe scheduling requirements that go beyond creating a local container.

A placement constraint and a placement preference have different implications. If an application requires a property, choosing a node without that property fails to meet the requirement. A preference permits a less desirable choice. A portable format needs to preserve that distinction even when implementations use different scheduling systems. Translating both into an undifferentiated hint would lose information the author supplied.

The deployment draft also distinguishes virtual-IP service discovery from DNS round-robin discovery. Its description says available values and defaults depend on the platform. A file can therefore communicate an intended mode while the implementation still has to determine whether its platform can provide it. This is a specific example of the work hidden inside the broad claim that a format supports multiple deployment targets.

The same reasoning applies to service metadata. The draft says deployment labels belong on the service, not on each container. An implementation needs a service concept, or an appropriate mapping to one, to retain that meaning. Copying the text of a label to every instance would not preserve the distinction the model makes between a service and its executions.

The opening draft still has version rules

The historical specification identifies itself as a work in progress with version 3.9. It requires a top-level version property and includes version 3.x and later, while leaving legacy 1.x and 2.x acceptance to implementations for compatibility. That is the draft available around this announcement. It should be read as a developing contract, not as a statement that every existing tool has already adopted its provisions.

File combination also has defined semantics. The draft describes overriding simple attributes and maps according to file order, appending lists, and resolving relative paths against the first file's parent directory. Those details are easy to omit in a high-level discussion of portability. They are also the sort of rules that can make two readers derive different applications from the same inputs if implementations disagree.

Consider an additional configuration file kept in another directory. Resolving its relative paths from that directory would produce different results from resolving them against the first file. Both approaches might seem reasonable in isolation. The specification chooses one, allowing an author to determine the resulting path without relying on a particular implementation's undocumented behavior. That agreement is as relevant to repeatability as the spelling of a service attribute.

The repository's use cases distinguish local development, Kubernetes deployment and cloud-provider integrations. It allows platform-specific capabilities through extensions or an accompanying configuration file. That acknowledges a limit to the shared model. Application authors may retain a common service description while supplying platform details separately where the general specification cannot express them adequately.

Governance makes semantics discussable

The governance document locates project decisions in repository changes. Anyone can propose and discuss a pull request. Maintainers must also use pull requests rather than pushing changes directly. The document defines reviewer and maintainer roles, with a process for adding maintainers based on sustained contribution and a vote by existing maintainers.

This gives a disagreement about the format a public place to be resolved. An implementer can propose a precise change to the contract, and application authors can inspect the discussion. Public governance does not by itself remove competing platform requirements. It provides a process through which those requirements can affect the same document instead of remaining separate assumptions inside different implementations.

For Compose users, the immediate benefit is a more explicit account of those assumptions. Build support can be absent. Deployment support has a separate contract. Resource identity and file merging have rules that implementations can discuss and implement. Those details determine how much of an application description remains meaningful when its execution environment changes, and they are now subjects of an independently developed specification.