Skip to content

Conversation

@i5okie
Copy link

@i5okie i5okie commented Nov 19, 2025

Add rootless Docker images for enhanced security and Kubernetes/OpenShift compatibility

Purpose

This PR adds rootless variants of the Caddy Docker images that run as a non-root user (UID 1001), making them suitable for security-constrained environments like Kubernetes and OpenShift, while remaining a drop-in replacement for the alpine images.

Why rootless?

Security best practices: Running containers as root is discouraged in production environments. Non-root containers provide defense-in-depth by limiting the impact of potential container breakouts or exploits.

Kubernetes/OpenShift requirements: Many Kubernetes clusters enforce Pod Security Standards that prohibit root containers. OpenShift, in particular, assigns arbitrary UIDs to containers by default and requires images to support this pattern.

Port restrictions: Non-root users cannot bind to privileged ports (< 1024). Using ports 80 and 443 in Kubernetes is problematic anyway since:

  • Services and Ingress controllers handle external traffic routing
  • Internal container ports are mapped through service definitions
  • Non-privileged ports (8080, 8443) are the standard convention

What's included

New image variants:

  • caddy:rootless - Rootless runtime image (ports 8080, 8443, 2019)
  • caddy:rootless-builder - Rootless builder image for custom Caddy builds with xcaddy

Key features:

  • Runs as UID 1001 with GID 0 (root group) for OpenShift compatibility
  • Automatically uses ports 8080/8443 via environment variables
  • Drop-in replacement for standard images - just change the tag
  • All directories writable by group 0 for arbitrary UID support
  • Uses the standard Caddyfile with automatic port substitution

Implementation details:
The rootless templates are based on the standard alpine templates with these modifications:

  • Removed setcap capability (not needed for non-privileged ports)
  • Added non-root user creation with adduser -D -u 1001 -g 0
  • Set proper ownership (chown 1001:0) and group permissions (chmod g+w) on all Caddy directories
  • Downloads the standard Caddyfile and uses sed to replace :80 with :{$CADDY_HTTP_PORT:8080}
  • Sets CADDY_HTTP_PORT=8080 and CADDY_HTTPS_PORT=8443 environment variables
  • Changed exposed ports from 80/443 to 8080/8443
  • Added USER 1001 directive to run as non-root
  • Users can mount custom Caddyfiles that reference these env vars or hardcode ports

Testing

Built and tested locally - serves the welcome page on port 8080 as expected. The image runs without root privileges and properly serves static content.

@i5okie
Copy link
Author

i5okie commented Nov 24, 2025

Is anyone available to take a look at this and offer feedback? There are several issues discussing the need for a rootless image.

Our team is working on several Hyperledger and OpenWallet Foundation projects utilizing Caddy server as a reverse proxy.
I've discovered that most of our deployments use older Caddy images and are working properly in a government maintained OpenShift platform. I'm working on creating helm charts for some of these projects. When attempting to use the latest caddy docker images, the deployments fail due to strict SCC rules in OpenShift. When developing helm charts which are meant to be used by the wider community, and be compatible with the government platform, using an initContainer to copy the binary into an arbitrary directory to bypass the issues, is not ideal. It does not look like a professional and robust solution to the problem. This PR should solve this issue. Not just for our projects, but for the wider community overall. And hopefully boost Caddy Server's use across the community :).

@francislavoie
Copy link
Member

Sorry for the wait. Thanks for this, I think it's the way we should go (separate tagged variant for rootless), rather than try to force the main one to support rootless (which is what prior discussion has always suggested).

There's still some things I think won't work well though, like the pki app trying to install its own root cert into /etc/ssl/certs which would require root. So that means if trying to use the rootless Caddy instance as an ACME server for mTLS, it wouldn't work well since it wouldn't trust connections made to servers which use a cert signed by Caddy's internal CA.

I want to get @hairyhenderson's thoughts on this before moving ahead with it though.

@hairyhenderson
Copy link
Contributor

Overall I'm supportive of this - @francislavoie and I have chatted about this and I'll let him make a few comments and get this merged. Thanks for your patience, @i5okie!


# Create non-root user with UID 1001 and root group (GID 0)
# OpenShift will override the UID but keep GID=0
RUN adduser -D -u 1001 -g 0 -H -h /usr/bin caddy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this isn't correct, Alpine uses busybox's adduser which has different flags. -g is "GECOS", not group ID. See https://linux.die.net/man/1/busybox (ctrl+f for adduser).

@@ -0,0 +1,47 @@
FROM golang:1.25-alpine3.22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a rootless builder? Is there a point to that? Can't the root builder be used anyway since it's only ever for multi-stage builds?

RUN chown -R 1001:0 /data /config /etc/caddy /usr/share/caddy /usr/bin/caddy

# Make directories writable by the root group for OpenShift compatibility
RUN chmod -R g+w /data /config /etc/caddy /usr/share/caddy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users shouldn't be putting any files in /usr/share/caddy, this is just for the default welcome index.html.

Suggested change
RUN chmod -R g+w /data /config /etc/caddy /usr/share/caddy
RUN chmod -R g+w /data /config /etc/caddy

Users would be writing their own Caddyfile (or more config files to import) to /etc/caddy/Caddyfile, so having that writable does make sense.

Of course /data and /config should also be writable so Caddy can work as expected.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should /usr/bin/caddy be writable by the rootless user ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it need to be @crntnvdl ?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RUN chown -R 1001:0 [...] /usr/bin/caddy effectively gives write permission to the rootless user to write the caddy binary, I don't think this should be allowed ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this thread is not a comment on that line, it just happens to be in the range github shows. That's a valid comment though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants