docker.recipes
Networking11 min read

How to Expose Docker Services Safely to the Internet

Learn secure methods for making your self-hosted services accessible from anywhere without compromising security.

01Introduction

Self-hosting is most useful when you can access services from anywhere. But exposing services to the internet is risky. This guide covers secure methods for remote access, from simple to advanced.

02Know the Risks

Before exposing anything, understand what you're opening up to. The internet is constantly scanned for vulnerable services. Any exposed port will be probed within minutes.

Exposed services will be attacked. Automated bots scan every IP address continuously. Your service must be secured before exposure.

03Always Use a Reverse Proxy

Never expose container ports directly. Always put a reverse proxy in front. This provides SSL termination, access logging, and a single point of security.
1# WRONG: Exposing container directly
2services:
3 app:
4 ports:
5 - "8080:80" # Accessible from internet if firewall allows
6
7# RIGHT: Behind reverse proxy
8services:
9 traefik:
10 ports:
11 - "443:443" # Only proxy is exposed
12
13 app:
14 # No ports exposed
15 labels:
16 - "traefik.enable=true"
17 - "traefik.http.routers.app.rule=Host(`app.example.com`)"
18 - "traefik.http.routers.app.tls=true"

04Add Authentication

If the service doesn't have built-in authentication, add it at the proxy level. Never expose unauthenticated services.
1# Traefik with basic auth
2services:
3 app:
4 labels:
5 - "traefik.http.routers.app.rule=Host(`app.example.com`)"
6 - "traefik.http.middlewares.app-auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"
7 - "traefik.http.routers.app.middlewares=app-auth"
8
9# Generate password hash:
10# htpasswd -nb admin yourpassword
11# Escape $ as $$ in compose files
12
13# For better security, use Authelia or Authentik
14# for SSO with 2FA support

Consider Authelia or Authentik for proper SSO with two-factor authentication instead of basic auth.

05Cloudflare Tunnel (Zero Trust)

Cloudflare Tunnel exposes services without opening firewall ports. Traffic goes through Cloudflare, providing DDoS protection and hiding your IP.
1services:
2 tunnel:
3 image: cloudflare/cloudflared:latest
4 command: tunnel run
5 environment:
6 - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
7 networks:
8 - internal
9
10 app:
11 image: myapp:1.0
12 networks:
13 - internal
14
15networks:
16 internal:
17
18# Setup:
19# 1. Create tunnel in Cloudflare Zero Trust dashboard
20# 2. Configure public hostname -> http://app:80
21# 3. No firewall ports needed!
22
23# Benefits:
24# - No exposed ports
25# - Built-in DDoS protection
26# - Hides your real IP
27# - Cloudflare Access for auth

Cloudflare Tunnel is free and doesn't require opening any inbound ports. It's excellent for home setups with dynamic IPs.

06Tailscale/WireGuard VPN

Instead of exposing to the public internet, use a VPN. Only devices on your VPN network can access services.
1# Tailscale sidecar approach
2services:
3 tailscale:
4 image: tailscale/tailscale:latest
5 hostname: myserver
6 environment:
7 - TS_AUTHKEY=${TAILSCALE_AUTHKEY}
8 - TS_STATE_DIR=/var/lib/tailscale
9 - TS_USERSPACE=false
10 volumes:
11 - tailscale_data:/var/lib/tailscale
12 - /dev/net/tun:/dev/net/tun
13 cap_add:
14 - NET_ADMIN
15 - NET_RAW
16 network_mode: service:app # Share network with app
17
18 app:
19 image: myapp:1.0
20 # Accessible via Tailscale IP/hostname
21
22volumes:
23 tailscale_data:
24
25# Access from any Tailscale-connected device:
26# http://myserver:80 or http://100.x.x.x:80

07Firewall Configuration

If you must expose ports publicly, restrict access with firewall rules. Only open what's necessary.
1# UFW (Ubuntu)
2# Only allow SSH and HTTPS
3ufw default deny incoming
4ufw default allow outgoing
5ufw allow 22/tcp
6ufw allow 443/tcp
7ufw enable
8
9# Allow specific IP only
10ufw allow from 203.0.113.0/24 to any port 22
11
12# iptables
13iptables -A INPUT -p tcp --dport 443 -j ACCEPT
14iptables -A INPUT -p tcp --dport 22 -j ACCEPT
15iptables -A INPUT -j DROP
16
17# Docker bypasses UFW by default!
18# Fix by editing /etc/docker/daemon.json:
19{
20 "iptables": false
21}
22# Then manage Docker traffic manually

Docker modifies iptables directly and can bypass UFW rules. Research 'Docker UFW' for your setup.

08Fail2ban for Brute Force Protection

Fail2ban monitors logs and bans IPs that show malicious behavior like repeated failed logins.
1# Install
2apt install fail2ban
3
4# Docker-aware configuration
5# /etc/fail2ban/jail.local
6[traefik-auth]
7enabled = true
8filter = traefik-auth
9logpath = /var/log/traefik/access.log
10maxretry = 5
11bantime = 3600
12
13# /etc/fail2ban/filter.d/traefik-auth.conf
14[Definition]
15failregex = ^<HOST> - .* 401 .*$
16
17# Restart
18systemctl restart fail2ban
19
20# Check status
21fail2ban-client status traefik-auth

09Geographic Restrictions

If you only access from certain countries, block the rest. Reduces attack surface significantly.
1# With Traefik and GeoIP plugin
2services:
3 traefik:
4 labels:
5 # Whitelist countries
6 - "traefik.http.middlewares.geoblock.plugin.geoblock.countries=US,CA,GB"
7
8# With Cloudflare (if using tunnel/proxy)
9# Configure in Cloudflare dashboard:
10# Security -> WAF -> Create rule -> Country not in [list] -> Block
11
12# With nginx (GeoIP module)
13# geoip_country /usr/share/GeoIP/GeoIP.dat;
14# map $geoip_country_code $allowed_country {
15# default no;
16# US yes;
17# CA yes;
18# }

10Monitoring and Alerts

You must monitor exposed services. Set up alerts for unusual activity.
1# Uptime Kuma for monitoring
2services:
3 uptime-kuma:
4 image: louislam/uptime-kuma:latest
5 volumes:
6 - uptime_data:/app/data
7 labels:
8 - "traefik.http.routers.status.rule=Host(`status.example.com`)"
9
10# Set up monitors for:
11# - HTTP/HTTPS endpoints
12# - SSL certificate expiry
13# - Response time thresholds
14# - Keyword presence
15
16# Configure notifications:
17# - Email
18# - Slack/Discord
19# - Pushover/Gotify
20# - Webhook

Monitor from outside your network to catch issues that internal checks would miss.

11Exposure Checklist

Before exposing any service: - [ ] Service behind reverse proxy - [ ] HTTPS enabled with valid certificate - [ ] Authentication required (at app or proxy level) - [ ] Firewall configured (only necessary ports) - [ ] Fail2ban or similar protection - [ ] Monitoring and alerts set up - [ ] Regular security updates planned - [ ] Backup strategy in place - [ ] Know how to quickly disable access if compromised