From chainguard-codegen
Converts existing Dockerfiles to Chainguard images by updating FROM statements with equivalents, migrating package managers to apk, adjusting users/permissions, backing up originals, and testing builds/runs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/chainguard-codegen:migrating-dockerfiles-to-chainguardThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
To convert a Dockerfile to use Chainguard images you should follow these steps:
To convert a Dockerfile to use Chainguard images you should follow these steps:
FROM statements to use Chainguard images.apkIf you're asked to convert a Dockerfile to Chainguard, before doing anything else, you should clarify the following with the user:
ORGANIZATION in
these instructions.-fips. For instance: python-fips.cgr.dev? Or, if images are
hosted/proxied from another repository, what is that repository?Store these answers under ~/.claude/chainguard-preferences.md and try and reuse
this file across subsequent asks to migrate Dockerfiles.
You should backup the old Dockerfile by prepending old. to it. For instance,
Dockerfile -> old.Dockerfile.
Then you can modify the original file directly.
You should replace images defined in FROM lines with the equivalent
Chainguard image.
Always use the mapping-container-images-to-chainguard skill if it is
available.
You should try to match to actively maintained tags when converting Dockerfiles.
You should prefer -dev tags as they have more utilities in them and they are
less likely to cause issues. One known exception is chainguard-base which has
no -dev variant. You should always use the latest tag for chainguard-base.
If the existing reference uses a digest reference (i.e
python:3.12-slim-bookworm@sha256:a866731a6b71c4a194a845d86e06568725e430ed21821d0c52e4efb385cf6c6f)
then you should also include a digest. Naturally, the digest will be different
for the Chainguard image. You can figure out the digest in a few ways, depending
on the available tooling:
# With crane (prefer this if it is available)
crane digest cgr.dev/ORGANIZATION/python:3.12-dev
# Or, with docker
docker pull cgr.dev/ORGANIZATION/python:3.12-dev
docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/ORGANIZATION/python:3.12-dev
Chainguard have their own Linux distribution, so they don't have equivalent
images for alpine, debian, ubuntu etc.
Where the Dockerfile is using one of these generic bases, you should swap it
to use chainguard-base (or chainguard-base-fips for FIPS). You should
always use the latest tag with chainguard-base. There is no latest-dev
tag for chainguard-base, so don't use that.
Where there are multiple FROM lines in a Dockerfile, you should try to use a
non-dev variant (i.e cgr.dev/ORGANIZATION/python:3.12) for the final stage
unless there are any RUN lines that follow it (in which case you need a shell,
and therefore a -dev image).
A common issue with multi stage builds is that files are created in the first
stage by the root user, which are then not accessible to the 65532 user in
the final stage. You can mitigate this by ensuring files copied into the final
stage belong to the 65532 user:
COPY --from=build --chown=65532:65532 /app .
apkUnlike other Linux distributions that may use apt, yum or dnf to install
packages, Chainguard images use apk to manage packages.
This means that you should convert instances of apt, yum and dnf to apk
in RUN lines.
For instance, for apt, lines like this:
RUN apt-get update \
&& apt-get install -y software-properties-common=0.99.22.9 \
&& add-apt-repository ppa:libreoffice/libreoffice-still \
&& apt-get install -y libreoffice \
&& apt-get clean
Should become:
RUN apk --no-cache add libreoffice
And lines like this:
RUN yum update -y && \
yum -y install httpd php php-cli php-common && \
yum clean all && \
rm -rf /var/cache/yum/*
RUN dnf update -y && \
dnf -y install httpd php php-cli php-common && \
dnf clean all
RUN microdnf update -y && \
microdnf -y install httpd php php-cli php-common && \
microdnf clean all
Should become:
RUN apk add --no-cache httpd php php-cli php-common
Don't just remove apt lines. Replace them with apk equivalents.
Chainguard images typically run as a non root user with the UID 65532, rather
than root. You will need to switch to root before running apk add and
then revert back to 65532 after.
USER root
RUN apk add --no-cache httpd php php-cli php-common
USER 65532
As mentioned above, Chainguard images typically run as a non root user with the
UID 65532, rather than root. Commonly, this user is called nonroot, but
not always. For instance, in the node image the user is called node.
To be safe, always prefer the UID when issuing USER statements or changing
the permissions of files.
You should switch to the root user with USER root before running privileged
commands (like npm) and then use USER 65532 after to return to the non root
user.
You can figure out which user the image is configured to use by querying the image configuration. How you do that depends on the tools you have available:
# With crane.
crane config cgr.dev/chainguard/python | jq -r '.config.User'
# With docker.
docker inspect --format='{{ .Config.User }}' cgr.dev/chainguard/python
You can also find this information in the Specifications tab of the Chainguard Image Directory:
https://images.chainguard.dev/directory/image/python/specifications
Unlike upstream images, the entrypoint for Chainguard base images is typically
the runtime interpreter for the given language (i.e java, python, php)
rather than a shell like sh or bash.
This means that a CMD like CMD ["python", "app.py"] will cause an error in a
Chainguard images because it translates to running python python app.py.
We should prefer to use explicit entrypoints like:
ENTRYPOINT ["python", "app.py"]
It's common in Dockerfiles that build PHP applications to install composer in this way:
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
This is unnecessary when using the Chainguard php image because composer is
already included in the -dev tags. Therefore, you can remove any lines that
install composer.
If you get errors complaining about a missing sh or bash, then ensure you
are using a -dev image, which provides a shell.
If you get errors complaining about permissions, then it's probably to
do with the non root user. Switch to USER root temporarily and then switch
back to USER 65532 once you've completed the privileged operation.
The Overview page for the Chainguard image typically includes information that is helpful when performing a migration.
https://images.chainguard.dev/directory/image/<image-name>/overview
If you run into issues, it is worth checking this page for guidance.
npx claudepluginhub chainguard-demo/claude-plugins --plugin chainguard-codegenHardens Docker/container images and runtime deployments with secure base images, non-root users, CVE scanning, SBOM/signing, seccomp/AppArmor, and Kubernetes pod security controls.
Provides Docker security guidelines covering image selection, minimal bases, build/runtime protections, network isolation, least privilege, and defense-in-depth strategies. Useful for securing containers.
Generates optimized multi-stage Dockerfiles, .dockerignore, for Node.js, Python, Go, Java apps with security hardening, layer caching, validation, and error fixes.