2. Container image
Overview#
The Dynatrace Enablement Framework leverages a custom Docker image as the foundation for all training and demo environments. This image is engineered for maximum compatibility, flexibility, and ease of use across a variety of platforms and deployment scenarios.
Key Features#
🖼️ Base Image:#
The framework uses mcr.microsoft.com/devcontainers/base:ubuntu as its base image, ensuring seamless compatibility with GitHub Codespaces and Visual Studio Code Dev Containers.
💻 Cross-Platform Support:#
The image is built to run on both AMD and ARM architectures, eliminating vendor lock-in and supporting a wide range of hardware.
☁️ Local and Cloud Execution:#
- Supports GitHub Codespaces for cloud-based development.
- Enables local execution on Windows, Linux, and macOS via Multipass, ensuring a consistent development environment regardless of host OS.
Dynatrace Integration#
Dynatrace OneAgent FullStack and Kubernetes CloudNativeFullstack deployments are fully supported. All required components—including the CSI Driver, Webhook, ActiveGate, and OneAgents—can be deployed within this image, ensuring comprehensive monitoring and observability for running applications.
Tooling#
🛠️ Baked into the image
Installed at image build time, present the moment the container starts:
- Kubectl
- Helm
- Kind
- Docker CLI — the client only; it talks to the host daemon through the mounted socket (see below)
- NodeJs
- Python (
python3-pip) - GitHub CLI (
gh)
🧰 Installed on demand by the framework
Kept out of the image so their versions can move without an image rebuild. A repo's
post-create.sh calls the installer it needs:
Docker-in-Socket Strategy#
The Dynatrace Enablement Framework uses a Docker-in-socket strategy to enable container management from within the development container. By mounting the Docker socket (/var/run/docker.sock) from the host into the container, the development environment can communicate directly with the host's Docker daemon.
How It Works#
- The
entrypoint.shscript inside the container manages interactions with the Docker daemon. - By sharing the Docker socket, the container can execute Docker commands as if running directly on the host.
- This enables workflows such as building, running, and managing additional containers from within your Codespace or Dev Container.
Benefits#
- Consistency: Docker commands behave identically inside the container and on the host.
- Flexibility: Supports advanced scenarios such as running nested containers or orchestrating multi-container setups.
- Simplicity: No need to install Docker inside the container; it leverages the host’s Docker installation.
Example#
In the devcontainer.json, mount the Docker socket as follows:
Special Container Runtime Arguments#
The following runArgs configuration in devcontainer.json enhances the capabilities of the development container:
- --init: Runs an init process inside the container to handle zombie processes and signal forwarding, improving container stability.
- --privileged: Grants the container extended privileges, allowing access to all host devices and enabling operations typically restricted in standard containers. This is useful for scenarios requiring low-level system access (e.g., running Docker inside Docker or accessing host resources).
- --network=host: Shares the host’s networking stack with the container, enabling direct use of the host’s network interfaces. This is helpful for networking tests or when services inside the container need to be accessible on the host network.
Image Distribution#
The image is hosted on Docker Hub and is cross-compiled for ARM and AMD architectures.
Versions and tags#
Everything that changes the image is pinned in .devcontainer/Dockerfile, so a rebuild reproduces
the tag: base mcr.microsoft.com/devcontainers/base:3.0.10-noble (by digest), NODE_VERSION (exact nodesource
package), NPM_MAJOR, DOCKER_CLI_VERSION, KUBECTL_VERSION, KIND_VERSION and HELM_VERSION. Downloads use curl -f,
so a failed fetch fails the build instead of saving an error page as a binary.
A changed image is published under a new tag and never rebuilt over an existing one. A retag
would make rollback impossible and hide drift. v1.3 is v1.2's toolset with Node 22.23.4,
which fixes CVE-2026-111497 (Brotli decoder out-of-bounds write).
v1.3.1 pins the Docker CLI to 27.5.1, the client v1.2 shipped. Ubuntu's docker.io moved to
29.x, whose client speaks API 1.52 and has no fallback to older daemons. The GitHub Codespaces host
daemon tops out at API 1.43, so every docker command in a v1.3 Codespace failed with client
version 1.52 is too new. k3d kept working because it talks to the API itself. The 27.x client
negotiates down. The CLI is a static Go binary, so it does not link the Brotli C library.
v1.3.2 is v1.3.1 with the entrypoint fix (ENH-075, #186). On a container's first start the
socket's docker GID differs from the image's, so /entrypoint.sh runs groupmod and execs its
command. The groupmod persists, so after docker restart or a stop/start the GIDs match. Up to
v1.3.1 the script then returned without running its command and the container exited 0: a
Codespace that was stopped and started came back in recovery mode. From v1.3.2 it execs the
command on that path too.
v1.4.0 clears the stale layers v1.3.2 carried. The base moves to devcontainers base v0.4.35
(2026-10-09), pinned by digest, and the build runs apt-get upgrade so the OS packages match the
Ubuntu security archive on the day it is built. The client tools are upstream's signed release
binaries, each asserted by version: helm v3.22.1, kubectl v1.37.1, kind v0.33.0 and the
Docker CLI 28.5.2, plus the GitHub CLI 2.103.3 from its release instead of Ubuntu's 2.45.0.
The image no longer installs Ubuntu's docker.io: it was there only for its docker group, and
it brought a Docker daemon, containerd and runc that nothing runs, because the host socket is
mounted. The group is created directly and /entrypoint.sh maps its GID as before. 28.5.2 is the newest client that still negotiates down to the Codespaces
daemon's API 1.43. 29.x does not, so the CLI stays on 28.x until that daemon moves. kubectl, kind
and the Docker CLI were still built with Go older than 1.26.9, so the Go standard library advisory
GO-2026-6612 remains in them until upstream ships rebuilt binaries (28.x never will). Use v1.4.0.
Publishing a new tag#
The Publish image workflow (.github/workflows/publish-image.yaml) builds .devcontainer/ for
linux/amd64 and linux/arm64 and pushes shinojosa/dt-enablement:<tag>. Run it by hand from
the Actions tab on the branch to build (normally main), giving the tag. It refuses a tag that
already exists on Docker Hub, refuses a release tag (v*) from any branch but main, and also
refuses when the lookup itself fails, so a rate limit
cannot read as "free". After pushing, it pulls the image on both architectures and prints the OS,
node -v and the tool versions. Throwaway test tags (for example entrypoint-test-186) go
through the same workflow. It needs the repository secrets DOCKERHUB_USERNAME and DOCKERHUB_TOKEN.
Publishing a tag does not move anything that uses the image. Each devcontainer.json, Orbital's
TEST_IMAGE and the 29 training repos pick a tag explicitly. They change through a framework
release and the registry sync.
Using the Image in devcontainer.json#
You can configure your development container to use the pre-built image or build it yourself from a Dockerfile, depending on your requirements.
Using the Pre-built Image#
To use the pre-built image, specify the "image" property in your devcontainer.json file:
// Pulling the image from the Dockerhub, runs on AMD64 and ARM64. Pulling is normally faster.
"image":"shinojosa/dt-enablement:v1.4.0",
Building the Image with VS Code#
If you want to build the image yourself (for example, to customise it), you need to use the "build" section in your devcontainer.json. Uncomment or add the following:
Comment out or remove the "image" line. Uncomment or add the "build" section, pointing to your Dockerfile. This will instruct the environment to build the image locally using your Dockerfile.
Cross-Compiling with Buildx#
In the .devcontainer folder, there is a Makefile that includes a buildx target specifically designed for cross-compiling the container image.
To use cross-compilation:
- Make sure your host architecture is ARM.
- Run the
buildxtarget from theMakefileto build the image for multiple architectures.
Example usage: