Enterprise teams need consistent, governed development environments for Ansible automation content. A single monolithic container image either bloats with every team’s dependencies or satisfies no one. Different automation domains (network, Windows, cloud, config-as-code) each require different system-level packages, and the Ansible DevTools container image has /var read-only at runtime by design. Container immutability is a feature, not a limitation: you don’t want developers running dnf install inside their workspaces, because that creates drift.
This guide demonstrates a tiered image layering strategy using standard OpenShift build primitives (ImageStreams and BuildConfigs) to produce customized workspace images for each automation domain without breaking container immutability. When the upstream base image receives a security patch, the entire chain rebuilds automatically, with no manual intervention at any tier.
graph LR
A["<b>Tier 0</b><br/>Base image<br/><i>Red Hat</i>"] -->|layer| B["<b>Tier 1</b><br/>Org image<br/><i>Platform team</i>"]
B -->|layer| C["<b>Tier 2</b><br/>Team image<br/><i>Team lead</i>"]
C -.->|opt-in| D["<b>Tier 3</b><br/>Personal image<br/><i>Individual</i>"]
Operational Impact: None – creates BuildConfigs and ImageStreams on the cluster for development environments. No production system mutation.
Business Value Drivers:
Technical Value Drivers:
Red Hat OpenShift Dev Spaces provides browser-based VS Code environments running on OpenShift. Each workspace is defined by a devfile.yaml checked into the project repository. When a developer clicks Create Workspace, Dev Spaces reads the devfile, provisions the environment, and presents a ready-to-use IDE. Zero local dependencies, zero configuration. For a full introduction to Ansible Development Tools and all installation methods (including Dev Spaces getting started), see the AI-Assisted Ansible Developer Experience guide.
The challenge starts when multiple teams share the same base image but need different system-level packages:
libssh-devel, python3-netaddr, paramikokrb5-workstation, python3-pykerberoshttpie, python3-pyyamlawscli, python3-boto3Because the Ansible DevTools container image has /var read-only at runtime, developers cannot dnf install additional packages, and that’s correct. Making /var writable is an anti-pattern that breaks container immutability and introduces environment drift, the exact problem Dev Spaces solves. Instead, customizations must be baked into the image at build time using standard container layering.
This guide uses a tiered container image strategy to deliver governed development environments. Each tier layers on top of the previous using standard OpenShift build primitives:
| Persona | Challenge | What They Gain |
|---|---|---|
| Platform Engineer | Delivering governed environments across multiple automation domains with different system-level dependencies | A repeatable image management pattern using standard OpenShift primitives that auto-rebuilds when upstream updates |
| Automation Architect | Standardizing toolchains across teams while allowing domain-specific customization | Tiered image model where standards are inherited automatically, not documented and hoped-for |
| Team Lead | Getting team-specific packages into workspaces without waiting on the platform team | Self-service Containerfile in the team repo, auto-built by a BuildConfig with no platform team bottleneck |
| Concern | Owner | Mechanism |
|---|---|---|
| Base image version (Tier 0) | Red Hat | External releases |
| Org-wide extra packages (Tier 1) | Platform team | Org-wide BuildConfig (inline Containerfile) |
| Team-specific packages (Tier 2) | Team lead | Containerfile in team workspace repo + BuildConfig |
| Personal extras (Tier 3) | Individual developer | Fork of team repo + personal BuildConfig (opt-in) |
oc CLI authenticated to the cluster with permissions to create BuildConfigs and ImageStreams in your target namespaceThe walkthrough uses the Red Hat supported image. A community alternative is available for development and testing without a subscription.
| Variant | Image | Subscription |
|---|---|---|
| Supported | registry.redhat.io/ansible-automation-platform-27/ansible-devspaces-rhel9 (catalog) |
AAP or Ansible Developer |
| Community | ghcr.io/ansible/ansible-devspaces |
None |
Tip: These are Dev Spaces images, not dev container images.
The DevTools guide uses
ansible-dev-tools-rhel9(supported) andcommunity-ansible-dev-tools(community) for local dev containers. Those are different images. Do not interchange them.
The tiered strategy uses OpenShift ImageStreams to track upstream image changes and BuildConfig triggers to cascade rebuilds automatically. When the upstream Ansible DevTools project publishes a new image version, the Tier 0 ImageStream detects the change through scheduled polling (importPolicy.scheduled: true). This triggers the Tier 1 BuildConfig, which layers org-wide packages and pushes the result to the Tier 1 ImageStream. The Tier 2 BuildConfig watches the Tier 1 ImageStream and rebuilds the team image automatically. If a developer has opted into a personal layer (Tier 3), that rebuilds as well. The entire cascade completes in minutes with zero manual intervention.
When a BuildConfig itself changes (for example, a team lead adds a new package to their Containerfile and pushes to Git), a ConfigChange trigger fires a rebuild of that tier and everything downstream.
The result: security patches from the upstream image propagate to every developer’s workspace automatically, and package customizations at any tier trigger only the rebuilds that need to happen.
| Maturity | What You Do |
|---|---|
| Crawl | Use the base image as-is (Tier 0 only) – no customization, works out of the box for general Ansible development |
| Walk | Add a Tier 1 org-wide image with an inline BuildConfig for common packages. Validate the auto-rebuild from Tier 0. This is the minimum viable setup for most organizations |
| Run | Add Tier 2 team images layered on the org-wide image. Each automation domain gets its own Containerfile and BuildConfig. Auto-rebuild cascade validated end-to-end |
| Fly | For organizations with 5+ domain variants, adopt a CEKit (Container Environment Kit) factory model for Tier 1: generate Containerfiles from YAML definitions, build all variants in parallel via CI, and publish to a shared registry. Add lifecycle automation to clean up stale personal images |
Operational Impact: None
Create an ImageStream that tracks the upstream Ansible DevTools image. The importPolicy.scheduled: true setting tells OpenShift to poll for new tags periodically and trigger downstream rebuilds when the image changes.
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: ansible-devspaces-base
spec:
tags:
- name: latest
from:
kind: DockerImage
name: registry.redhat.io/ansible-automation-platform-27/ansible-devspaces-rhel9:latest
importPolicy:
scheduled: true
referencePolicy:
type: Local
Tip: Community image alternative.
For development or testing without a subscription, replace the
namevalue withghcr.io/ansible/ansible-devspaces:latest.
Apply with oc apply -f imagestream-base.yaml.
Operational Impact: None
Create a BuildConfig with an inline Containerfile that layers org-common packages on top of the base image. The dockerStrategy.from field connects the build to the Tier 0 ImageStream, overriding the FROM line at build time. Two triggers ensure automatic rebuilds: ImageChange fires when the base image updates, and ConfigChange fires when the BuildConfig itself is modified.
First, create the output ImageStream:
apiVersion: image.openshift.io/v1
kind: ImageStream
metadata:
name: ansible-devspaces-org
spec: {}
Then the BuildConfig:
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
name: ansible-devspaces-org
spec:
source:
type: Dockerfile
dockerfile: |
FROM ansible-devspaces-base:latest
USER root
RUN dnf install -y \
pinentry-curses \
jq \
&& dnf clean all
USER 1000
strategy:
type: Docker
dockerStrategy:
from:
kind: ImageStreamTag
name: ansible-devspaces-base:latest
output:
to:
kind: ImageStreamTag
name: ansible-devspaces-org:latest
triggers:
- type: ImageChange
imageChange: {}
- type: ConfigChange
Apply both with oc apply -f imagestream-org.yaml -f buildconfig-org.yaml. The build starts automatically on creation.
Operational Impact: None
Each team maintains a Containerfile in their workspace repository. A BuildConfig in the team’s namespace layers team-specific packages on top of the org-wide image.
Team Containerfile (in the team workspace repo root). The FROM line references the ImageStream name – the BuildConfig’s dockerStrategy.from field overrides it at build time to resolve through the ImageStream:
FROM ansible-devspaces-org:latest
USER root
RUN dnf install -y \
libssh-devel \
python3-netaddr \
&& dnf clean all
USER 1000
Team BuildConfig:
apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
name: devspaces-network-team
namespace: ansible-network-devspaces
spec:
source:
type: Git
git:
uri: https://github.com/myorg/ansible-network-workspace.git
ref: main
strategy:
type: Docker
dockerStrategy:
dockerfilePath: Containerfile
from:
kind: ImageStreamTag
namespace: <infra-namespace>
name: ansible-devspaces-org:latest
output:
to:
kind: ImageStreamTag
name: devspaces-network-team:latest
triggers:
- type: ImageChange
imageChange: {}
- type: ConfigChange
Replace <infra-namespace> with the namespace where the Tier 1 ImageStream lives. The ImageChange trigger watches the org-wide image – when it rebuilds (due to an upstream update or package change), the team image rebuilds automatically.
Operational Impact: None
Point the team’s devfile.yaml at the team ImageStream in the OpenShift internal registry. This is the moment where image management meets developer experience: after this step, any developer who opens this repository in Dev Spaces gets the team image automatically. They don’t need to know about BuildConfigs or ImageStreams.
schemaVersion: 2.2.2
metadata:
name: ansible-network-workspace
components:
- name: tooling-container
container:
image: image-registry.openshift-image-registry.svc:5000/ansible-network-devspaces/devspaces-network-team:latest
memoryRequest: 2Gi
memoryLimit: 4Gi
cpuRequest: 500m
cpuLimit: 1000m
Commit the devfile to the team workspace repository. Developers log into the Dev Spaces dashboard, paste the repository URL, click Create & Open, and start coding with all team-specific packages pre-installed.
Operational Impact: None
When a developer needs a system package that no one else on the team requires, they can create a personal image layer. The developer forks the team workspace repo, modifies the Containerfile to layer on the team image, and updates the devfile to point at their personal ImageStream.
FROM devspaces-network-team:latest
USER root
RUN dnf install -y niche-package-xyz && dnf clean all
USER 1000
The personal BuildConfig follows the same pattern as Tier 2, with the team image as the base. If the same package shows up in multiple personal layers on the same team, promote it to Tier 2.
Tip: Tier 3 is opt-in, not the standard path.
Most developers will never need a personal layer. For one-off experiments, use nested Podman inside the workspace instead of building a custom image.
Trigger the cascade by adding a harmless package to the Tier 1 BuildConfig. Edit the inline Containerfile to add tree (a small, safe package), apply the change, and watch the builds propagate:
# After editing the Tier 1 BuildConfig inline Containerfile to add 'tree'
oc apply -f buildconfig-org.yaml
# Watch the cascade
oc get builds -w
Tier 1 build completes, then Tier 2 build starts automatically within minutes:
NAME TYPE FROM STATUS STARTED
ansible-devspaces-org-2 Docker Running Just now
ansible-devspaces-org-2 Docker Complete 3 minutes ago
devspaces-network-team-2 Docker Running Just now
devspaces-network-team-2 Docker Complete 5 minutes ago
Copyright 2026 Red Hat, Inc.