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:
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¶
Build:
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:
boto3andbotocorefor AWSnetaddrfor IP address filterspywinrmfor Windows hostskubernetesfor Kubernetes modulesrequestsfor 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