Your options
There are three ways to reach your stack from outside, and they are not equally safe. Pick by who needs access, not by which is easiest to set up.
| Approach | Who can reach it | Needs |
|---|---|---|
| VPN | Only devices you enrol | A port forward, or a mesh service |
| Tunnel | Anyone with the URL, plus whatever auth you add | An account with the tunnel provider |
| Reverse proxy on a public IP | Anyone on the internet | A domain, a port forward, and real care |
The honest default for a personal workspace server is a VPN. Nothing is exposed, there is no login page for anyone to attack, and every app behaves as though you were at home. Publish only the specific things other people genuinely need to open.
Reverse proxy
The proxy's job is TLS and hostname routing. Give each service a subdomain and let the proxy hold the certificate.
A Caddy site block is two lines:
wiki.example.com {
reverse_proxy bookstack:80
}
Three things go wrong often enough to name:
- The app needs to know its own URL. BookStack has
APP_URL, Paperless-ngx hasPAPERLESS_URL, Homarr infers it from forwarded headers. Set it, or you get redirects back to localhost and broken links in emails. - WebSockets. CryptPad, Wiki.js, AppFlowy and Jitsi all need them passed through. Caddy and Traefik do this by default; Nginx needs the upgrade headers set explicitly.
- Certificate validation. The Selkies-based containers serve their own self-signed certificate on port 3001, so a proxy that validates upstream certificates will refuse them. Their HTTP port exists for exactly this case.
VPN
Two shapes, and the difference is whether you open a port.
WireGuard on your own server means one UDP port forwarded and a config file per device. It is fast, it is small, and it depends on nobody. It needs a port forward, which means it needs a router you control and an address that does not change, or dynamic DNS.
A mesh VPN such as Tailscale or Netbird does the same job without a port forward, by having both ends connect out to a coordination service. Easier, works behind carrier-grade NAT, and the tradeoff is that a third party brokers your connections. Both have self-hostable coordination servers if that matters to you.
Tunnels
A tunnel service gives you a public hostname without a port forward or a static IP. An agent on your machine connects out, and traffic comes back down that connection. Cloudflare Tunnel is the common choice; there are others.
Two things to weigh. The provider terminates TLS, so they can see your traffic. And providers have terms about what may be served through a free tunnel, particularly around large media files. Read the current terms rather than a blog post about them, because the wording has changed more than once and old summaries circulate long after they stop being true.
For a workspace server, tunnels fit well: the traffic is documents and web pages, not bulk media. If you want a public URL for one shared thing, a tunnel in front of that one service is a reasonable answer.
Authentication
Proxying is not authenticating. If a service has no login, putting it on a subdomain with a valid certificate just means the world can reach it over HTTPS.
Three layers, weakest first:
- Basic auth at the proxy. One shared password, no sessions, no recovery. Fine for a single admin tool on a home network, nothing more.
- A forward-auth gateway. Authelia or Authentik sit beside the proxy and require a login before any request reaches the app. This is the right answer for apps with weak or no built-in authentication.
- The app's own accounts, plus SSO. Best where available. Forgejo can act as an OAuth2 provider for other services, which is worth knowing if you are running it anyway. PdfDing, BookStack, Homarr and Dashy all support OIDC.
What never gets published
Some containers in this list should not be published to the internet, with or without a proxy in front of them. If the generator writes you a reverse-proxy config, these are excluded from it, and the config says why.
| Module | Why |
|---|---|
| Obsidian (LinuxServer) | Streamed desktop with a terminal and passwordless sudo. No authentication by default |
| Calligra (LinuxServer) | Same base image, same exposure |
| Anything with the Docker socket | The socket is root on the host, in practical terms |
The Docker socket deserves its own note because it appears innocently.
Homarr asks for it to show container status, and dashboards generally do.
Mounting /var/run/docker.sock gives that container control of
the Docker daemon, which is equivalent to root on the host. Mount it read
only when you do mount it, understand that read only limits the damage rather
than removing it, and leave it out entirely if you do not need live container
status.
One more, which is about data rather than exposure. CryptPad needs two different origins, a main domain and a sandbox domain with restrictive content-security-policy headers. Its documentation states that running without the sandbox may put users' information at risk. A single-hostname CryptPad install is not a working CryptPad install.