Skip to content

Allow Caddy to bind privileged ports as www-data#2321

Open
Binero wants to merge 1 commit intopelican-dev:mainfrom
Binero:main
Open

Allow Caddy to bind privileged ports as www-data#2321
Binero wants to merge 1 commit intopelican-dev:mainfrom
Binero:main

Conversation

@Binero
Copy link
Copy Markdown

@Binero Binero commented May 6, 2026

This adds the cap_net_bind_service file capability to the Caddy binary, letting it bind :80/:443 as the unprivileged www-data user the image already drops to.

RUN apk add --no-cache --virtual .setcap-deps libcap \
    && setcap cap_net_bind_service+ep /usr/sbin/caddy \
    && apk del .setcap-deps

This would allow us to listen on port 443 when using host networking.

The reason to want host networking is that bridge mode obscures real client IPs and doesn't do public IPv6 without significant Docker daemon reconfiguration. The former additionally means that in access logs rather than the real user IP showing up, it will show Docker's internal virtual IP address, which is the main reason I want to pitch this PR.

Another way to achieve the same is to run the entire container as root, but that would defeat the existing USER www-data directive, and basically boils down to deploying a nuke to squash a mosquito.

People who are using bridge networking are unaffected by this change; they already have privilege over :80/:443 by default.

Grant cap_net_bind_service to /usr/sbin/caddy so it can bind :80/:443
without running as root. Previously this only worked in bridge networking,
where Docker sets net.ipv4.ip_unprivileged_port_start=0 in the container
netns; under network_mode: host the host sysctl applies and Caddy fails
with "bind: permission denied" on :443.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 782310b6-e90a-4916-bf65-982f2a8a63a9

📥 Commits

Reviewing files that changed from the base of the PR and between 38620a9 and ccf1ece.

📒 Files selected for processing (2)
  • Dockerfile
  • Dockerfile.dev

📝 Walkthrough

Walkthrough

Both production and development Dockerfiles are updated to grant the Caddy binary capability to bind to privileged ports (80/443) without container elevation. libcap is installed temporarily, setcap assigns cap_net_bind_service+ep to the Caddy binary, and the tool is removed afterward.

Changes

Privileged Port Binding via Capabilities

Layer / File(s) Summary
Capability Configuration
Dockerfile, Dockerfile.dev
Both production and development final stages install libcap, use setcap to grant cap_net_bind_service+ep to /usr/sbin/caddy, then remove the temporary package. Enables non-root privileged port binding for ports 80/443.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: allowing Caddy to bind privileged ports while running as the unprivileged www-data user.
Description check ✅ Passed The description is directly related to the changeset, explaining the motivation for adding cap_net_bind_service capability to the Caddy binary and its implications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Binero
Copy link
Copy Markdown
Author

Binero commented May 6, 2026

I have read the CLA Document and I hereby sign the CLA

@rmartinoscar
Copy link
Copy Markdown
Member

rmartinoscar commented May 7, 2026

Hey thanks for your contribution, I don't think we should do it this way the current docker and log config to get the proper client ip header seems fine but we'll see what @parkervcp has to say about it, he is the one mainly managing docker stuff.

@mristau
Copy link
Copy Markdown
Contributor

mristau commented May 7, 2026

in my caddy logs i have the correct client_ip, just remote_ip is the one my proxy is on, that one is always my docker network gateway

@Binero
Copy link
Copy Markdown
Author

Binero commented May 8, 2026

I was surprised by your seemingly different results, but I figured it out, and the answer is somewhere in the middle. The client_ip does show up correctly for external connections. I tested it from localhost, using the localhost address, and ordinarily you would expect localhost to be the client IP, but in this case, it does incorrectly show the IP address of the Docker proxy instead. Connecting from the surprisingly outside doesn't seem to have this issue.

So even though the main reason I gave why someone might not want to run over the bridge network might be more limited as I first understood it to be, there are many reasons why one might want to run over the host network (e.g. cleaner IPv6 support), and this small change would support those setups without requiring people to undo some of the security hardening the image already has built-in.

The addition to the Dockerfile seems to be self-contained enough to not pose any significant maintenance burden, while still opening the door for this common type of setup.

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