Last month, we improved Podman’s support for Dev Containers on Windows. Dev Containers is a popular specification for containerized developments that is becoming a standard. In this blog post, we review the concept of “Feature” in Dev Containers and how Podman currently supports it on different OSes.
Development Containers
Development Containers https://containers.dev/, also known as Dev Containers, is a file format for specifying a containerized development environment. With a devcontainer.json file, a team can declare the tools and configurations to build and run a project. It can be versioned in the project’s git repository so anyone can start a development environment in seconds. The only requirement is a container engine such as Docker or Podman.
Microsoft has created the standard. Visual Studio Code, the JetBrains IDEs, and many other tools support it.
Dev Containers Images
Microsoft maintains a list of pre-built dev container images. Every image ships the development tools for a specific programming language or tech stack. The list of Microsoft pre-built images can be found at https://github.com/devcontainers/images. For example, the image for Go development is ghcr.io/devcontainers/templates/go.
{
"image": "mcr.microsoft.com/devcontainers/go"
}
These images have a predefined set of tools for a given language. Nevertheless, more is needed for real-world projects. It’s possible to use a custom image instead of the Microsoft pre-build ones, but Dev Containers features are a popular way to customize Dev Containers.
Dev Containers Features
Features are extra tools that can be included on top of an image, and the list of available features (official and community-supported) is published here: https://containers.dev/features. In the following example, tools such as bats
, pandoc
, pre-commit
, shellcheck
, modern-shell-utils
, and skopeo
are added to the tools of the Microsoft Go image:
{
"image": "mcr.microsoft.com/devcontainers/go",
"features": {
"ghcr.io/edouard-lopez/devcontainer-features/bats:0": {},
"ghcr.io/rocker-org/devcontainer-features/pandoc:1": {},
"ghcr.io/prulloac/devcontainer-features/pre-commit:1": {},
"ghcr.io/marcozac/devcontainer-features/shellcheck:1": {},
"ghcr.io/mikaello/devcontainer-features/modern-shell-utils:2": {},
"ghcr.io/jsburckhardt/devcontainer-features/skopeo:1": {}
}
}
Features are container-based, too, and, unlike images, are built at the startup of the development environment.
Features internals and the use of additional build contexts
The way features are installed is particular; we will detail it here. It will be helpful to clarify how Podman supports them.
Features are distributed as an OCI artifact with the type “application/vnd.devcontainers
“. When the Devcontainer is started, using the VisualStudio Code, for example, the OCI artifact is pulled, and an installation script is extracted in a local folder (this is what the installation script for the skopeo feature looks like).
$ skopeo inspect --raw docker://ghcr.io/jsburckhardt/devcontainer-features/skopeo:1 | jq
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.devcontainers",
"digest": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
},
...
}
The next step in the Dev Container startup is to build the custom image. This is done on the fly, based on the devcontainer.json image (`mcr.microsoft.com/devcontainers/go` in the example above), and executing the features installation scripts. Here is a (simplified) example of a generated Dockerfile for building a Dev Container custom image:
FROM mcr.microsoft.com/devcontainers/go
COPY --from=features-context /tmp/build-features/ /tmp/dev-container-features
RUN cd /tmp/dev-container-features/skopeo_3 \
&& chmod +x ./devcontainer-features-install.sh \
&& ./devcontainer-features-install.sh
The interesting part here is the COPY –from instruction. It uses the additional build context feature-context and not the default one. The folder tmp/build-features/ exists in the build context feature-context. This is an elegant way to isolate the build context of the Dev Container image and the features folders to avoid collision problems (e.g. a build context subfolder and the feature folder may have the same name).
That means that to support Dev Containers features, a container engine needs to support additional build contexts.
Podman support for additional build contexts
Additional build contexts are a reasonably recent feature. They have been introduced in Podman v4.2 and Buildah v1.26.
podman build \
--build-context context=./alt-ctx \
./main-ctx
The current implementation of the additional build contexts works fine when podman runs locally. But it isn’t when podman runs on a remote machine. In fact, unlike the main build context, additional contexts aren’t sent over to the remote machine, and the build fails because the additional build contexts’ local paths don’t exist in the remote machine.
This is an annoying limitation and will hopefully be addressed soon. But for now, Podman Machine can support additional build contexts (and Dev Containers features) with some default conventions.
Podman Machine default conventions to support additional build contexts
First, Podman Machine mounts some default folders into the guest OS.
Linux | macOS | Windows | |||
Host Path | Guest Path | Host Path | Guest Path | Host Path | Guest Path |
$HOME | $HOME | /Users | /Users | C:\ | /mnt/C |
/private | /private |
Second, Windows host paths (e.g. C:/Users/user1
) are automatically transformed into Linux guest paths (e.g. /mnt/c/Users/user1
). The Podman client for Windows uses this trick when mounting volumes, too (e.g. podman run -v C:/Users/user1
).
📝 These conventions are supported on Windows WSL starting with Podman 5.3.0 (there is a separate issue for Hyper-V).
The following table summarizes the current Dev Containers Features (and Additional Build Context) support for the different OSes.
Linux local | Linux remote | macOS (vfkit) | macOS (libkrun) | Windows (WSLv2) | Windows (Hyper-V) |
🟢 | ❌ | 🟠 | 🟠 | 🟠 | ❌ |
🟢 Works
🟠 Works with default Podman machine configuration
❌ Doesn’t work yet
Inception: Building Podman with a Podman-backed Dev Container
Now that Podman has decent support for Dev Containers features let’s make the most of it. Let’s build Podman itself from sources! The only requirements should be having locally installed:
- Podman
- The devcontainer CLI (VisualStudio Code with the Dev Containers extension works, too).
Once these two requirements are installed, this simple devcontainer.json
and Dockerfile
are enough to build Podman.
{
"name": "podman-devcontainer",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"features": {
"ghcr.io/devcontainers/features/go:1": {}
},
"containerEnv": {
"HOME": "/home/vscode"
}
}
FROM quay.io/podman/stable
COPY /rpm/podman.spec /rpm/podman.spec
RUN dnf update -y && dnf -y install 'dnf-command(builddep)'
RUN dnf -y \
builddep /rpm/podman.spec
These two files should be copied into a new folder named .devcontainer
at the root of the podman repository (or checkout the branch named inception of my podman fork).
The following command, run from a terminal at the root of the Podman repository, starts the Dev Container.
$ devcontainer up --docker-path podman --workspace-folder .
Then, the command make podman can be executed in the Dev Container.
$ devcontainer exec --workspace-folder . --docker-path podman \
make podman
Alternatively, the same Dev Container can be started with VisualStudio Code.
📝 Running other make targets of the Podman Makefile may fail. That may be related to some missing tools in the Dev Container or to the Podman Machine’s insufficient resources (RAM and CPU). Either way, this devcontainer.json
should be easy to improve.
Leave a Reply