How to transition to secure container images with new migration guides

Adrian Mouat, Staff DevRel Engineer
May 22, 2024

Is your container security strategy keeping pace with your container adoption? If you're looking to bolster the integrity of your container images, Chainguard's new migration guides might be the answer.

Why migrate to Chainguard Images?

There are several reasons to migrate your container images to Chainguard:

  • Minimal: Drastically reduced size with only essential components.
  • Low-to-no CVEs: Significantly fewer vulnerabilities due to reduced attack surface.
  • Continuous maintenance: Daily updates to address security concerns and vulnerabilities.

Five tips for migrating to Chainguard Images

1. Use -dev Images when you need a shell

Chainguard Images have no shell or package manager by default. This is great for security, but sometimes you need these things, especially for build stages in mutli-stage Dockerfiles and for debugging. For these cases there are -dev image variants which do include a shell and package manager.

For example, if I try to get a shell in the image:

-- CODE language-bash -- docker run -it --entrypoint /bin/sh --user root docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/bin/sh": stat /bin/sh: no such file or directory: unknown.

But this is possible with the latest-dev variant:

-- CODE language-bash -- docker run -it --entrypoint /bin/sh --user root / # apk add php fetch (1/6) Installing xz (5.4.6-r0) (2/6) Installing libxml2 (2.12.6-r0) (3/6) Installing php-8.2-config (8.2.18-r0) (4/6) Installing readline (8.2-r3) (5/6) Installing sqlite-libs (3.45.1-r0) (6/6) Installing php-8.2 (8.2.18-r0) OK: 66 MiB in 38 packages / #

2. You can install a different shell

The -dev images and wolfi-base images use the ash shell from BusyBox by default. This is nice from a minimalist perspective, but it's not so great if you need to port a bash-and-Debian-centric entrypoint script to Chainguard Images.

In these cases you have a choice — you can persevere and update your scripts to work in ash, or you can simply install the shell that works with your scripts. There's no reason to be stuck on the ash shell if you really need bash or zsh.

For example:

-- CODE language-bash -- docker run -it 423450e3fd52:/# echo {1..5} {1..5} 423450e3fd52:/# apk add bash fetch (1/3) Installing ncurses-terminfo-base (6.4_p20231125-r1) (2/3) Installing ncurses (6.4_p20231125-r1) (3/3) Installing bash (5.2.21-r1) OK: 20 MiB in 17 packages 423450e3fd52:/# bash 423450e3fd52:/# echo {1..5} 1 2 3 4 5 423450e3fd52:/#

3. Use apk search

Following on from the last point, you'll often need to install extra utilities to provide required dependencies for applications and scripts. These dependencies are likely to have different package names compared to other Linux distributions, so the apk search command can be very useful for finding the package you need.

For example, say we are porting a Dockerfile that uses the groupadd command. We could convert this to the busybox addgroup equivalent, but it's also perfectly fine to add the groupadd utility. The only issue is that there's no groupadd package, so we have to search for it:

-- CODE language-bash -- docker run -it ae154854dc6d:/# groupadd /bin/sh: groupadd: not found ae154854dc6d:/# apk add groupadd ERROR: unable to select packages: groupadd (no such package): required by: world[groupadd] ae154854dc6d:/# apk search groupadd shadow-4.15.1-r0 ae154854dc6d:/# apk add shadow (1/4) Installing libmd (1.1.0-r1) (2/4) Installing libbsd (0.12.2-r0) (3/4) Installing linux-pam (1.6.1-r0) (4/4) Installing shadow (4.15.1-r0) OK: 20 MiB in 18 packages ae154854dc6d:/# groupadd Usage: groupadd [options] GROUP Options: -f, --force exit successfully if the group already exists, and cancel -g if the GID is already used -g, --gid GID use GID for the new group -h, --help display this help message and exit -K, --key KEY=VALUE override /etc/login.defs defaults -o, --non-unique allow to create groups with duplicate (non-unique) GID -p, --password PASSWORD use this encrypted password for the new group -r, --system create a system account -R, --root CHROOT_DIR directory to chroot into -P, --prefix PREFIX_DIR directory prefix -U, --users USERS list of user members of this group

Another useful trick is the "cmd:" syntax for finding packages that provide commands. For example, searching for ldd returns multiple results:

-- CODE language-bash -- ae154854dc6d:/# apk search ldd dpkg-dev-1.22.6-r0 nfs-utils-2.6.4-r1 posix-libc-utils-2.39-r1

But if we use the cmd: syntax we only get a single result:

-- CODE language-bash -- ae154854dc6d:/# apk search cmd:ldd posix-libc-utils-2.39-r1

And we can even use the syntax directly in apk add:

-- CODE language-bash -- ae154854dc6d:/# apk add cmd:ldd (1/4) Installing ncurses-terminfo-base (6.4_p20231125-r1) (2/4) Installing ncurses (6.4_p20231125-r1) (3/4) Installing bash (5.2.21-r1) (4/4) Installing posix-libc-utils (2.39-r1) OK: 27 MiB in 22 packages

4. Watch out for entrypoint differences

The entrypoint on Chainguard images is often different to other common images. This is due to the lack of a shell in our images, but it can be confusing.

For example, if I run Docker Hub's official Python image, it opens the Python interpreter by default:

-- CODE language-bash -- docker run -it python Python 3.12.3 (main, Apr 10 2024, 11:26:46) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>

And the Chainguard Image works in the same way:

-- CODE language-bash -- docker run -it Python 3.12.3 (main, Apr 9 2024, 16:36:34) [GCC 13.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> exit()

But if I pass a Linux command to the Docker Hub image, it will be run from a shell:

-- CODE language-bash -- docker run -it python echo "in a shell" in a shell

This is made possible by a clever entrypoint script in the Docker Hub image. As we don't have a shell in the Chainguard Image, it instead tries to parse the command as an argument to the Python interpreter:

-- CODE language-bash -- docker run -it echo "in a shell" /usr/bin/python: can't open file '//echo': [Errno 2] No such file or directory

Because we wanted to keep the entrypoint consistent between our standard and -dev images, the same results are seen when running

-- CODE language-bash -- docker run -it echo "in a shell" /usr/bin/python: can't open file '//echo': [Errno 2] No such file or directory

5. Wolfi is not the same as Alpine Linux

Chainguard Images use the Wolfi Linux Distribution. This distribution uses the apk packaging format pioneered by Alpine, but it is a completely separate distribution to Alpine.

A particularly important difference is that Wolfi packages are all compiled against glibc, which is the most common standard C library, unlike Alpine which uses musl. Chainguard chose glibc for compatibility reasons — glibc is the standard C library used by most of the industry.

Because our toolchains and dependencies are different, it's not possible to mix Wolfi packages with Alpine packages in the same image.

Migration guides for smooth transition

To facilitate a smooth and successful migration, Chainguard has developed a series of comprehensive image migration guides. These guides cover the following platforms:

Each of these reference docs provide a compatibility overview between Chainguard Images and the third-party images of their respective platform. Each includes a table highlighting what binaries and scripts are contained in Chainguard's Wolfi BusyBox and coreutils packages versus their counterparts. This will support developers in being able to quickly understand what utils are available and how they may need to transition their current applications effectively. 

Additionally, we've published a general migration guide that outlines the changes one would need to make to an existing Dockerfile — whether it currently uses Debian, Red Hat UBI, or Alpine — to instead use Chainguard Images.

The goal for these guides is to help you migrate your existing container images to Chainguard with minimal disruption. If there is a migration guide you don’t see here but would find helpful, reach out to our team.

Secure your container images with Chainguard

By migrating your container images to Chainguard, you can significantly fortify the security and compliance of your container-based applications. With Chainguard's image migration guides, you can transition your existing images to secure Chainguard Images and enjoy the benefits of improved security, simplified compliance, and increased efficiency.

Get started with Chainguard Image migration

To get started with Chainguard image migration, visit our website and find the relevant guide for your platform. If you have any questions you can reach out or join one of our live Learning Labs.

Related articles

Ready to lock down your supply chain?

Talk to our customer obsessed, community-driven team.