Skip to content

Extending images

Create a derived image when automation needs extra operating-system packages, Python libraries, Galaxy collections, roles, or wrapper scripts. Pin the base tag so builds are reproducible.

This guide uses:

FROM willhallonline/ansible:2.21-alpine-3.24

See image tags for available variants.

The base images include uv/uvx and run as the non-root ansible user by default. The Python examples below switch to root for the image build, use uv pip for system installation, and switch back to ansible at the end. pip remains available for compatibility, but uv pip matches the upstream image build path.

Do not bake secrets

Do not copy SSH keys, Vault passwords, cloud credentials, or private tokens into Dockerfiles. Inject secrets at runtime with mounts or CI secrets.

Add OS packages

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
RUN apk add --no-cache \
      git \
      openssh-client \
      rsync \
      curl \
      jq
USER ansible

WORKDIR /ansible
FROM willhallonline/ansible:2.20-ubuntu-24.04

USER root
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      git \
      openssh-client \
      rsync \
      curl \
      jq \
 && rm -rf /var/lib/apt/lists/*
USER ansible

WORKDIR /ansible
FROM willhallonline/ansible:2.18-rockylinux-10

USER root
RUN dnf install -y \
      git \
      openssh-clients \
      rsync \
      curl \
      jq \
 && dnf clean all
USER ansible

WORKDIR /ansible

Build:

docker build -t registry.example.com/platform/ansible:2.21-alpine .

Add Python libraries

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
RUN uv pip install --system --break-system-packages --no-cache \
      boto3 \
      botocore \
      netaddr \
      pywinrm \
      kubernetes

USER ansible
WORKDIR /ansible

Common control-node libraries:

  • boto3 and botocore for AWS
  • netaddr for IP address filters
  • pywinrm for Windows hosts
  • kubernetes for Kubernetes modules
  • requests for HTTP APIs

Install Galaxy collections

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
WORKDIR /build
COPY requirements.yml requirements.yml
RUN ansible-galaxy collection install \
      -r requirements.yml \
      -p /usr/share/ansible/collections

ENV ANSIBLE_COLLECTIONS_PATH=/usr/share/ansible/collections
USER ansible
WORKDIR /ansible

Example requirements.yml:

collections:
  - name: community.general
    version: 9.5.0
  - name: ansible.posix
    version: 1.6.2
  - name: kubernetes.core
    version: 3.2.0

Install roles

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
WORKDIR /build
COPY requirements.yml requirements.yml
RUN ansible-galaxy role install \
      -r requirements.yml \
      -p /usr/share/ansible/roles

ENV ANSIBLE_ROLES_PATH=/usr/share/ansible/roles
USER ansible
WORKDIR /ansible

Combine OS, Python, and Galaxy content

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
RUN apk add --no-cache git openssh-client rsync jq

RUN uv pip install --system --break-system-packages --no-cache boto3 netaddr pywinrm kubernetes

WORKDIR /build
COPY requirements.yml requirements.yml
RUN ansible-galaxy collection install -r requirements.yml -p /usr/share/ansible/collections

ENV ANSIBLE_COLLECTIONS_PATH=/usr/share/ansible/collections
USER ansible
WORKDIR /ansible

Run your derived image:

docker run --rm -it \
  -v $(pwd):/ansible \
  --workdir=/ansible \
  registry.example.com/platform/ansible:2.21-custom \
  ansible-playbook -i inventory.ini site.yml

Custom entrypoint

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

USER ansible
WORKDIR /ansible
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["ansible-playbook", "site.yml"]

Example entrypoint:

#!/bin/sh
set -e

if [ -f requirements.yml ]; then
  ansible-galaxy collection install -r requirements.yml -p collections
fi

exec "$@"

Note

Keep entrypoints simple and visible. Hidden setup can make CI failures harder to diagnose.

Multi-stage builds

FROM willhallonline/ansible:2.21-alpine-3.24 AS builder

USER root
RUN apk add --no-cache build-base python3-dev
RUN uv pip wheel --wheel-dir /wheels cryptography

FROM willhallonline/ansible:2.21-alpine-3.24

USER root
COPY --from=builder /wheels /wheels
RUN uv pip install --system --break-system-packages --no-cache /wheels/* \
 && rm -rf /wheels

USER ansible
WORKDIR /ansible

Use this pattern when build tools are needed only temporarily.

Pin and push

docker build -t registry.example.com/platform/ansible:2.21.4-20260714 .
docker push registry.example.com/platform/ansible:2.21.4-20260714

Use the pushed image in Compose or CI:

services:
  ansible:
    image: registry.example.com/platform/ansible:2.21.4-20260714
    working_dir: /ansible
    volumes:
      - .:/ansible