Run the internet-facing apps on gVisor, one at a time #39

Open
opened 2026-10-15 21:09:03 +00:00 by hutao · 0 comments
Owner

Docker here is rootful with no user-namespace remapping (docker info lists only seccomp and cgroupns), so uid 0 in a container is uid 0 on the host. Several internet-facing apps run that way, with docker's default capabilities and a writable root filesystem: forgejo, webmail (roundcube), kuma, navidrome, and searxng (which at least has no-new-privileges). An RCE in any of them lands as root in a container, one kernel bug away from the host.

Proposal

Register gVisor's runsc as an extra docker runtime and move those apps onto it one at a time, with --runtime=runsc in each container's extraOptions. Their syscalls then hit gVisor's user-space kernel instead of the host's.

  • Platform: systrap, gVisor's default. It needs no hardware virtualisation; this VPS has no nested virt (see the runner chapter), which only rules out the KVM platform.
  • Opt-in per container, so each app moves and rolls back on its own.
  • First app is the canary: kuma or searxng. No throwaway containers on this box (a failure pages), so the canary is a real deploy, watched and rolled back if it misbehaves.

Fit, app by app

App Fit
kuma, searxng, webmail, navidrome good: internet-facing, light on syscalls; navidrome's music bind mount goes through gVisor's file layer, slower but fine
forgejo probably fine, slower: git is file-heavy, measure clone and push before and after
caddy later, if at all: already unprivileged with almost no capabilities, so the least to gain, and every site's traffic would pay gVisor's network-stack cost
mailserver risky: its fail2ban bans with firewall rules inside the container, and gVisor's netfilter support is limited
minecraft, minecraft2 no: performance-sensitive, and already fenced off from the host's private ports
dozzle no: talks to the docker socket

Measure

  • Per moved app: memory overhead of the sandbox, response time of its main pages, and for forgejo, clone/push time on a large repo.
  • The app's own health checks and kuma's monitors stay green.

Cheaper, and worth doing either way

Drop all capabilities and add back only what each app needs, plus no-new-privileges, on every container that does not have them. That narrows the same escape for nothing, and does not conflict with gVisor.

Related: #38 (network per app).

Docker here is rootful with no user-namespace remapping (`docker info` lists only `seccomp` and `cgroupns`), so uid 0 in a container is uid 0 on the host. Several internet-facing apps run that way, with docker's default capabilities and a writable root filesystem: forgejo, webmail (roundcube), kuma, navidrome, and searxng (which at least has `no-new-privileges`). An RCE in any of them lands as root in a container, one kernel bug away from the host. ## Proposal Register gVisor's `runsc` as an extra docker runtime and move those apps onto it one at a time, with `--runtime=runsc` in each container's `extraOptions`. Their syscalls then hit gVisor's user-space kernel instead of the host's. - **Platform**: systrap, gVisor's default. It needs no hardware virtualisation; this VPS has no nested virt (see the runner chapter), which only rules out the KVM platform. - **Opt-in per container**, so each app moves and rolls back on its own. - **First app is the canary**: kuma or searxng. No throwaway containers on this box (a failure pages), so the canary is a real deploy, watched and rolled back if it misbehaves. ## Fit, app by app | App | Fit | |---|---| | kuma, searxng, webmail, navidrome | good: internet-facing, light on syscalls; navidrome's music bind mount goes through gVisor's file layer, slower but fine | | forgejo | probably fine, slower: git is file-heavy, measure clone and push before and after | | caddy | later, if at all: already unprivileged with almost no capabilities, so the least to gain, and every site's traffic would pay gVisor's network-stack cost | | mailserver | risky: its fail2ban bans with firewall rules inside the container, and gVisor's netfilter support is limited | | minecraft, minecraft2 | no: performance-sensitive, and already fenced off from the host's private ports | | dozzle | no: talks to the docker socket | ## Measure - Per moved app: memory overhead of the sandbox, response time of its main pages, and for forgejo, clone/push time on a large repo. - The app's own health checks and kuma's monitors stay green. ## Cheaper, and worth doing either way Drop all capabilities and add back only what each app needs, plus `no-new-privileges`, on every container that does not have them. That narrows the same escape for nothing, and does not conflict with gVisor. Related: #38 (network per app).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
hutao/vps#39
No description provided.