Skip to content

Universal image build and reuse semantics #74

@Chuxel

Description

@Chuxel

As dev container features (#61) are introduced into the spec and related CLI and tools/services, pre-building an image that includes features using a devcontainer.json file becomes critically important. However, devcontainer.json's semantics currently support referencing an image or a Dockerfile/Docker Compose file. This means that you end up needing two devcontainer.json files to pre-build: one for the pre-build, and one for actual use with the image reference. While this works, ideally, we could have one file that can be used in either case. Even better, ideally this would work with any orchestrator.

To resolve this, I'd propose we decouple the idea of the build section from orchestration and enable referencing an image name to act as the primary label for a build. This label is then what is used by default when doing the build (unless a different label is passed into the dev container CLI via the --image-name argument). The image name can then be referenced either in the devcontainer.json file, or in a Docker Compose file.

This could be enabled through the introduction of an imageNames property into the build section of devcontainer.json. If specified, the logic would be to do the following:

  1. First try to pull the image referenced in the image property - if this works, use it.
  2. If this fails, build it using the contents of the build property features, and give it the names specified under imageNames. Then use the image.
  3. If a rebuild is fired, then would ignore any centrally pushed images and then build as described in (2). An argument passed into the dev container CLI can also signal this should happen.

For Docker Compose or other orchestrators, the logic would be the same, but the image reference would just be in the orchestration format instead. Consider these examples...

Single container example:

{
    "image": "image-name-from-build",
    "build": {
        "imageNames": [ "image-name-from-build" ],
        "dockerfile": "Dockerfile"
    },
    "features": {
       "ghcr.com/devcontainers/features/docker-in-docker": {}
   }
}

Docker Compose:

devcontainer.json

{
    "dockerComposeFile": "docker-compose.yaml",
    "service": "foo",
    "workspaceFolder": "/workspace",
    "build": {
        "imageNames": [ "image-name-from-build" ],
        "dockerfile": "Dockerfile"
    },
   "features": {
       "ghcr.com/devcontainers/features/docker-in-docker": {}
   }
}

Related docker-compose.yaml:

#...
services:
  foo:
    image: image-name-from-build
#...

For cases where a base image is all that is needed beyond features, we could also allow for an baseImage property in the build section. e.g.,

{
    "image": "image-name-from-build",
    "build": {
        "imageNames": [ "image-name-from-build" ],
        "baseImage": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu"
    },
    "features": {
       "ghcr.com/devcontainers/features/docker-in-docker": {}
   }
}

This also allows for pre-builds as we consider long term support for supporting orchestrator formats in the dev container spec that do not inherently have a supported way to first do a build before spinning up a container.

Support for multi-container scenarios here can be handled in a similar way, though likely we want to at least introduce an array syntax to devcontainer.json to encapsulate multiple container image builds where appropriate as discussed in #10. For example:

[
    {
        "dockerComposeFile": "docker-compose.yaml",
        "service": "foo1",
        "workspaceFolder": "/workspace/foo1",
        "build": {
            "imageNames": [ "image-name-from-build-1" ],
            "dockerfile": "foo/Dockerfile"
        },
       "features": {
           "ghcr.com/devcontainers/features/docker-in-docker": {}
       }
    },
    {
        "dockerComposeFile": "docker-compose.yaml",
        "service": "foo2",
        "workspaceFolder": "/workspace/foo2",
        "build": {
            "imageNames": [ "image-name-from-build-2" ],
            "dockerfile": "foo2/Dockerfile"
        },
       "features": {
           "ghcr.com/devcontainers/features/node": {}
       }
    }
]

//cc @craiglpeters @chrmarti given we've had discussions along these lines to date. FYI @bamurtaugh @jkeech @alexdima

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalStill under discussion, collecting feedback

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions