VPN Inside a Container
Section titled “VPN Inside a Container”You can run a real VPN inside your container and keep every published URL working. Which of the two ways you start it decides whether that holds: run the WireGuard configuration file your provider gives you and the URLs keep answering, or run the provider’s desktop app and they go dark. Both use the same provider, the same relay, and the same encryption; only the outcome for your published URLs differs. Read the next section before you start either one.
Why a full tunnel breaks inbound URLs
Section titled “Why a full tunnel breaks inbound URLs”Your container’s URLs (*-terminal-1.*, *-console-1.*, every service you publish) are reached through the Hoody Reverse Proxy, which preserves the visitor’s real IP all the way into the container. That is deliberate: your app sees who is calling, and hooks, permissions, and audit records all depend on that address being the visitor’s.
Your container’s reply is therefore addressed to a public IP. A full-tunnel VPN (AllowedIPs = 0.0.0.0/0) sends everything public into the tunnel, that reply included. The answer leaves through your VPN provider and never reaches the visitor who asked.
The symptom is not an error page. The connection is accepted, TLS completes in about 5 ms, and then it hangs for roughly two minutes before failing. A packet capture shows the container answering correctly and the answer walking straight into the tunnel:
19:30:17.461 IP <container>.76 > <visitor>.39066: Flags [S.] (on the VPN interface)Zero matching packets appeared on the normal network interface over the same window. The container was never broken; its reply was leaving on the wrong interface.
Hoody keeps replies to inbound proxy traffic on the path they arrived on, so a full tunnel and your published URLs can coexist. What no routing rule can fix is a firewall that drops the request before any routing decision exists, which is exactly what a VPN app’s kill switch is.
Use the config file, not the app
Section titled “Use the config file, not the app”Download the WireGuard configuration from your provider and run it with wg-quick.
# WireGuard userspace toolsapt-get update && apt-get install -y wireguard-tools
# Put the config your provider gave you in place.# wg-quick names the interface after the filename.install -m 600 mullvad-sg-sin-001.conf /etc/wireguard/
# Upwg-quick up mullvad-sg-sin-001
# Downwg-quick down mullvad-sg-sin-001Measured on a live container with a real Mullvad WireGuard device on relay sg-sin-wg-001:
- With the tunnel up, the published URL returns HTTP 200 in about 33 ms.
- The URL survives the tunnel being restarted, verified across three full down/up cycles.
- Egress is protected. Container exit IP
138.199.60.6, andhttps://am.i.mullvad.net/connectedreports “You are connected to Mullvad (server sg-sin-wg-001)”. The server’s own IP is not exposed.
A VPN app with a kill switch, lockdown mode, or “block connections without VPN” applies a default-deny inbound firewall while connected. That drops the incoming connection before any routing decision happens, so no routing fix can help. There is nothing left to route.
Measured with the Mullvad desktop app on the same container:
- Kill switch armed: the published URL returns HTTP 000, and nothing answers.
- Kill switch removed: HTTP 200, immediately.
- “Allow local network sharing” enabled: still HTTP 000.
The last result is worth spelling out: the edge arrives carrying the visitor’s public IP. It is not local-network traffic, so a local-network exemption will never match it.
Turning the kill switch off to get your URLs back leaves you running a VPN app with its safety feature disabled. Run the config file instead.
Verify both directions
Section titled “Verify both directions”Check the tunnel and the URLs separately. They can fail independently, and knowing which one broke tells you where to look.
# Egress: are you actually on the relay?curl https://am.i.mullvad.net/connected# You are connected to Mullvad (server sg-sin-wg-001)# Ingress: does the published URL still answer?curl -o /dev/null -s -w '%{http_code}\n' \ https://{project}-{container}-terminal-1.{server}.containers.hoody.com/# 200000 means the request never got an answer, which points to a kill switch armed inside the container. Go in over SSH and take the VPN down.
What stops working while a full tunnel is up
Section titled “What stops working while a full tunnel is up”These destinations are container-outbound, so keeping your inbound replies on course does nothing for them. While any full tunnel is up:
| Destination | Address | Status with a full tunnel |
|---|---|---|
| Platform DNS resolver | 169.254.169.53 | Direct queries fail |
| AI proxy | 198.51.100.1 | Unreachable |
| APT / package proxy | 198.51.100.2 | Unreachable |
That list is narrower than it looks. General internet access works, and ordinary name resolution works, because it goes through the tunnel like everything else. What you lose is the platform’s own internal shortcuts, not your connectivity.
Recover access when URLs go dark
Section titled “Recover access when URLs go dark”If your URLs have already gone dark, there are two ways back in.
Go in over SSH and stop the VPN:
ssh -i ~/.ssh/hoody-container-1 root@ssh.$serverName.containers.hoody.com
# then, inside the containerwg-quick down mullvad-sg-sin-001# or quit the VPN appOr restart the container. VPN interfaces and their routing do not survive a restart; that behaviour was measured, not assumed. A restart returns the container to a clean network and your URLs come back with it.
How it works
Section titled “How it works”There is nothing for you to configure. When your container starts, Hoody installs a reply-path rule inside the container. Traffic your container initiates goes wherever your VPN sends it; traffic that answers the Hoody edge goes back to the edge.
Two consequences follow:
- The rule is installed at container start, before anything of yours runs. It has to be in place before a VPN starts, which is why it is done there and not later.
- A tunnel that is already running when the rule is installed takes precedence over it. That is another reason a restart fixes things: the restart puts the rule back in front.
How this differs from network configuration
Section titled “How this differs from network configuration”These are two different layers, documented on two different pages:
| Network Configuration | VPN inside the container (this page) | |
|---|---|---|
| Where it runs | On the host, outside your container | Inside your container, by you |
| What it speaks | SOCKS5, HTTP, HTTPS proxies | Whatever VPN client you install |
| Setup inside the container | None | Yours to install and run |
| Can the container bypass it | No | It is the container’s own tunnel |
Use Network Configuration when you want an exit IP change with no in-container setup and no way for the workload to escape it. Run a VPN inside the container when you need the provider’s actual tunnel: a specific relay, a WireGuard device, or a client only that provider ships.
Useful Questions
Section titled “Useful Questions”Can a VPN inside my container cut off my SSH access?
Section titled “Can a VPN inside my container cut off my SSH access?”No. SSH to a Hoody container does not travel over the container’s network at all. The host brokers it through the container runtime, so no route and no in-container firewall can reach it. Every step of the testing behind this page ran that way, with the tunnel up and the kill switch armed.
Will my published URLs survive a full tunnel?
Section titled “Will my published URLs survive a full tunnel?”Yes, with a WireGuard config file run by wg-quick. Measured: HTTP 200 in about 33 ms with the tunnel up, across three tunnel restarts.
Why does my provider’s app break them?
Section titled “Why does my provider’s app break them?”Because its kill switch is a default-deny inbound firewall. It drops the connection before there is any routing decision left to fix. Measured: HTTP 000 armed, HTTP 200 the moment it is removed.
Does “Allow local network sharing” fix the app?
Section titled “Does “Allow local network sharing” fix the app?”No. Measured HTTP 000 with it enabled. The Hoody edge arrives with the visitor’s real public IP, so a local-network exemption never matches it.
Does the tunnel survive a container restart?
Section titled “Does the tunnel survive a container restart?”No. VPN interfaces and their routing are gone after a restart. That rules out a tunnel that persists across restarts, and it is also what gets you out of trouble when one has locked you out.
Which VPN providers work?
Section titled “Which VPN providers work?”Any provider that publishes a downloadable WireGuard configuration. Mullvad is the one measured end to end here, on relay sg-sin-wg-001.
Do the platform resolver, AI proxy, and APT proxy work?
Section titled “Do the platform resolver, AI proxy, and APT proxy work?”No, not while a full tunnel is up: those are container-outbound and go into the tunnel. Ordinary DNS and general internet access are unaffected.
What’s Next
Section titled “What’s Next”Complete your networking setup:
- SSH Access → - Set this up first. A VPN cannot block it.
- Network Configuration → - Host-level exit IP routing, no in-container setup
- Firewall → - Host-enforced packet rules your container cannot touch
Key points:
- The Hoody edge preserves the visitor’s real IP, which is why a full tunnel would otherwise swallow your replies
- Replies to inbound proxy traffic stay on the path they arrived on
- A WireGuard config file keeps your URLs working; a VPN app’s kill switch takes them down
- SSH keeps working whatever the tunnel does
- A container restart clears any VPN state