From ab6f9011257fd2af56791d846ec1bbc347f8afbd Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Thu, 20 Aug 2026 16:20:10 -0500 Subject: [PATCH 1/3] Add rootless podman image Ports the podman image from the archived community-templates repo so it has a maintained home. Installs podman from Ubuntu's own repositories instead of the kubic unstable repo, and grants subuid/subgid ranges to the standard coder user instead of a separate podman user. --- README.md | 2 ++ images/podman/README.md | 28 ++++++++++++++++++++ images/podman/containers.conf | 16 ++++++++++++ images/podman/storage.conf | 30 ++++++++++++++++++++++ images/podman/ubuntu.Dockerfile | 45 +++++++++++++++++++++++++++++++++ scripts/images.sh | 1 + 6 files changed, 122 insertions(+) create mode 100644 images/podman/README.md create mode 100644 images/podman/containers.conf create mode 100644 images/podman/storage.conf create mode 100644 images/podman/ubuntu.Dockerfile diff --git a/README.md b/README.md index 4c2c0ce..b0af13b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ This repository contains example images for use with [Coder](https://coder.com/d - `example-java`: Contains Java development tools. - `example-node`: Contains Node.js development tools. - `example-desktop`: Contains a desktop environment accessible via web browser. +- `example-podman`: Contains rootless Podman for building and running + containers without a privileged runtime. ## Images on Docker Hub diff --git a/images/podman/README.md b/images/podman/README.md new file mode 100644 index 0000000..7c1baa8 --- /dev/null +++ b/images/podman/README.md @@ -0,0 +1,28 @@ +# Podman + +[![DockerPulls](https://img.shields.io/docker/pulls/codercom/enterprise-podman)](https://hub.docker.com/r/codercom/enterprise-podman) + +## Description + +Wraps [enterprise-base](../base/README.md) with rootless +[Podman](https://podman.io), so workspaces can build and run containers +without a privileged container runtime or custom RuntimeClass. `docker` is +aliased to `podman`. + +This image was previously published as `ghcr.io/coder/podman` from the +now-archived +[community-templates](https://github.com/coder/community-templates/tree/main/kubernetes-podman) +repository. + +## How To Use + +See +[Docker in Workspaces: Rootless Podman](https://coder.com/docs/admin/templates/extending-templates/docker-in-workspaces#rootless-podman) +for the template changes this image pairs with: an AppArmor `unconfined` +profile for the workspace container and a FUSE device exposed via +smarter-device-manager. + +Nodes must have Linux user namespaces enabled +(`sysctl user.max_user_namespaces` greater than 0). Notably, +[Bottlerocket](https://github.com/bottlerocket-os/bottlerocket) disables them +by default, and EKS Auto Mode nodes cannot enable them at all. diff --git a/images/podman/containers.conf b/images/podman/containers.conf new file mode 100644 index 0000000..e4aee2b --- /dev/null +++ b/images/podman/containers.conf @@ -0,0 +1,16 @@ +[containers] +netns="host" +userns="host" +ipcns="host" +utsns="host" +cgroupns="host" +cgroups="disabled" +log_driver = "k8s-file" +volumes = [ + "/proc:/proc", +] +default_sysctls = [] +[engine] +cgroup_manager = "cgroupfs" +events_logger="file" +runtime="crun" diff --git a/images/podman/storage.conf b/images/podman/storage.conf new file mode 100644 index 0000000..04eb09b --- /dev/null +++ b/images/podman/storage.conf @@ -0,0 +1,30 @@ +# Storage configuration for rootless Podman inside a Kubernetes pod. +# See man 5 containers-storage.conf for all options. + +[storage] +# Default storage driver. fuse-overlayfs (set as the mount program below) +# lets the overlay driver work for unprivileged users. +driver = "overlay" + +# Temporary storage location +runroot = "/run/containers/storage" + +# Primary read/write location of container storage +graphroot = "/var/lib/containers/storage" + +[storage.options] +# Read-only image stores shared into the image at build time. +additionalimagestores = [ + "/var/lib/shared", +] + +pull_options = {enable_partial_images = "false", use_hard_links = "false", ostree_repos=""} + +[storage.options.overlay] +# Mount via fuse-overlayfs instead of mounting overlayfs directly, which +# unprivileged users cannot do. Pairs with a FUSE device exposed to the pod +# (e.g. via smarter-device-manager). +mount_program = "/usr/bin/fuse-overlayfs" + +# Extra mount options +mountopt = "nodev,fsync=0" diff --git a/images/podman/ubuntu.Dockerfile b/images/podman/ubuntu.Dockerfile new file mode 100644 index 0000000..cd86d2b --- /dev/null +++ b/images/podman/ubuntu.Dockerfile @@ -0,0 +1,45 @@ +FROM codercom/enterprise-base:ubuntu + +# Run everything as root +USER root + +# Install Podman and rootless dependencies from Ubuntu's own repositories. +# uidmap provides newuidmap/newgidmap, which rootless Podman requires. +RUN apt-get update && \ + DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \ + podman \ + crun \ + fuse-overlayfs \ + slirp4netns \ + uidmap && \ + rm -rf /var/lib/apt/lists/* + +# Allow the unprivileged coder user to map subordinate UID/GID ranges for +# rootless containers. +RUN setcap cap_setuid+ep /usr/bin/newuidmap && \ + setcap cap_setgid+ep /usr/bin/newgidmap && \ + chmod 0755 /usr/bin/newuidmap /usr/bin/newgidmap && \ + echo "coder:100000:65536" >/etc/subuid && \ + echo "coder:100000:65536" >/etc/subgid + +COPY containers.conf /etc/containers/containers.conf +COPY storage.conf /etc/containers/storage.conf +RUN chmod 644 /etc/containers/containers.conf /etc/containers/storage.conf + +# Read-only shared image stores referenced by storage.conf. +RUN mkdir -p /var/lib/shared/overlay-images \ + /var/lib/shared/overlay-layers \ + /var/lib/shared/vfs-images \ + /var/lib/shared/vfs-layers && \ + touch /var/lib/shared/overlay-images/images.lock \ + /var/lib/shared/overlay-layers/layers.lock \ + /var/lib/shared/vfs-images/images.lock \ + /var/lib/shared/vfs-layers/layers.lock + +ENV _CONTAINERS_USERNS_CONFIGURED="" + +# Alias "docker" to "podman" +RUN [ -e /usr/bin/docker ] || ln -s /usr/bin/podman /usr/bin/docker + +# Set back to coder user +USER coder diff --git a/scripts/images.sh b/scripts/images.sh index b351ae5..b5f140d 100644 --- a/scripts/images.sh +++ b/scripts/images.sh @@ -36,4 +36,5 @@ IMAGES=( "desktop" "universal" "kitchensink" + "podman" ) From d985b3669020cd9532ec4c532606e245e93a4c34 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Thu, 20 Aug 2026 16:27:38 -0500 Subject: [PATCH 2/3] Fix node image build on modern Ubuntu apt-key was removed from the resolute base image, which has failed every build since Aug 10 (including scheduled rebuilds on main). Trust the Yarn repository via a signed-by keyring instead. Verified locally: node v24.19.0, yarn 1.22.22. --- images/node/ubuntu.Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/images/node/ubuntu.Dockerfile b/images/node/ubuntu.Dockerfile index f5f44dd..8317d11 100644 --- a/images/node/ubuntu.Dockerfile +++ b/images/node/ubuntu.Dockerfile @@ -8,10 +8,11 @@ RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - && \ DEBIAN_FRONTEND="noninteractive" apt-get update -y && \ apt-get install -y nodejs -# Install Yarn -RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - -RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list -RUN DEBIAN_FRONTEND="noninteractive" apt-get update && apt-get install -y yarn +# Install Yarn. apt-key was removed from modern Ubuntu releases, so trust +# the Yarn repository via a signed-by keyring instead. +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor -o /usr/share/keyrings/yarnkey.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ + DEBIAN_FRONTEND="noninteractive" apt-get update && apt-get install -y yarn # Set back to coder user USER coder From 1d1ed557996f380730a1d1f214b0cb0de0adbb86 Mon Sep 17 00:00:00 2001 From: Ben Potter Date: Thu, 20 Aug 2026 17:58:47 -0500 Subject: [PATCH 3/3] Address review feedback - Drop the no-op _CONTAINERS_USERNS_CONFIGURED env (Podman sets it internally after re-exec; an empty value reads the same as unset) - Document that runroot/graphroot are rootful-only and rootless storage lands on the home volume - README wording: See -> Visit --- images/podman/README.md | 5 ++++- images/podman/storage.conf | 10 ++++++++-- images/podman/ubuntu.Dockerfile | 2 -- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/images/podman/README.md b/images/podman/README.md index 7c1baa8..96735f2 100644 --- a/images/podman/README.md +++ b/images/podman/README.md @@ -16,12 +16,15 @@ repository. ## How To Use -See +Visit [Docker in Workspaces: Rootless Podman](https://coder.com/docs/admin/templates/extending-templates/docker-in-workspaces#rootless-podman) for the template changes this image pairs with: an AppArmor `unconfined` profile for the workspace container and a FUSE device exposed via smarter-device-manager. +Rootless container storage lives under `~/.local/share/containers/storage`, +so it lands on the workspace home volume with no extra mounts required. + Nodes must have Linux user namespaces enabled (`sysctl user.max_user_namespaces` greater than 0). Notably, [Bottlerocket](https://github.com/bottlerocket-os/bottlerocket) disables them diff --git a/images/podman/storage.conf b/images/podman/storage.conf index 04eb09b..207ece3 100644 --- a/images/podman/storage.conf +++ b/images/podman/storage.conf @@ -1,15 +1,21 @@ # Storage configuration for rootless Podman inside a Kubernetes pod. # See man 5 containers-storage.conf for all options. +# +# Note: runroot and graphroot below only apply when Podman runs as root. +# Rootless Podman (the expected mode for this image, running as the coder +# user) stores containers under ~/.local/share/containers/storage instead, +# so no extra volume mounts are required for those paths. [storage] # Default storage driver. fuse-overlayfs (set as the mount program below) # lets the overlay driver work for unprivileged users. driver = "overlay" -# Temporary storage location +# Temporary storage location (rootful only; see note above) runroot = "/run/containers/storage" -# Primary read/write location of container storage +# Primary read/write location of container storage (rootful only; see note +# above) graphroot = "/var/lib/containers/storage" [storage.options] diff --git a/images/podman/ubuntu.Dockerfile b/images/podman/ubuntu.Dockerfile index cd86d2b..b43e848 100644 --- a/images/podman/ubuntu.Dockerfile +++ b/images/podman/ubuntu.Dockerfile @@ -36,8 +36,6 @@ RUN mkdir -p /var/lib/shared/overlay-images \ /var/lib/shared/vfs-images/images.lock \ /var/lib/shared/vfs-layers/layers.lock -ENV _CONTAINERS_USERNS_CONFIGURED="" - # Alias "docker" to "podman" RUN [ -e /usr/bin/docker ] || ln -s /usr/bin/podman /usr/bin/docker