You can test websites locally by typing localhost + port in the browser. But to expose services to the public internet so others can access your URL, you need more configuration. There are two common methods: Nginx and tunneling.
The conclusion: Nginx is better for cloud servers, while services on home mini PCs are better suited for tunneling. Cloud servers usually have public IPs with open ports 443. After configuration, you can directly access via domain name. If your server is at home, HTTP 80 and HTTPS 443 are usually blocked by ISPs to prevent unauthorized commercial website hosting.
One solution is to use a different port instead of 443, then use Nginx to forward to 443. However, accessing services would require yourdomain.com:4433, which feels inelegant. The second solution is using tunneling, with Cloudflared being a common option.
Nginx
Using Ports 80/443
-
First, create a new folder. Let’s assume it’s in
~, so the structure is:~ βββ nginx β βββ docker-compose.yml βββ service1 β βββ docker-compose.yml β βββ volume βββ service2 βββ docker-compose.yml βββ volume -
Edit Nginx’s
docker-compose.yml. We useLet's Encryptfor automatic HTTPS certificates:services: nginx-proxy: image: jwilder/nginx-proxy container_name: nginx-proxy restart: unless-stopped ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ./certs:/etc/nginx/certs:ro - ./vhost.d:/etc/nginx/vhost.d - ./html:/usr/share/nginx/html - ./conf.d:/etc/nginx/conf.d networks: - nginx letsencrypt: image: jrcs/letsencrypt-nginx-proxy-companion container_name: nginx-letsencrypt restart: unless-stopped environment: - NGINX_PROXY_CONTAINER=nginx-proxy volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./certs:/etc/nginx/certs - ./vhost.d:/etc/nginx/vhost.d - ./html:/usr/share/nginx/html depends_on: - nginx-proxy networks: - nginx networks: nginx: external: true -
Create a Docker network named
nginx:docker network create nginx -
Start the container:
docker compose up -dNow Nginx and Let’s Encrypt are running. When exposing services (as shown in the Vaultwarden article), you can use Nginx as a reverse proxy.
Using Other Ports
Just change the ports to something else, like:
ports:
- "8080:80"
- "8443:443"
Note: Let’s Encrypt still needs port 80 for certificate renewal, which cannot be changed. If these ports are blocked, you can only manually update certificates.
Cloudflared
Cloudflared is Cloudflare’s tunneling client. The basic principle is: install a local client that communicates with Cloudflare’s servers, creating a tunnel. When users visit your site, Cloudflare receives the request and forwards it to the mini PC, enabling public internet access.
With this method, you don’t even need a public IP or open ports. Cloudflare provides basic protection. However, access is limited by Cloudflare, domestic access might be slow, and you need a domain.
Cloudflare Configuration
-
There are multiple ways to configure Cloudflared. I use token mode, where all configuration is in Cloudflare’s cloud and the local client only keeps the token to connect. This way, if you switch devices, you only need to remember the token.
-
Get credentials from Cloudflare. Enable Zero Trust and choose the free plan.
-
Network β Tunnels β Add a tunnel
-
Select Cloudflared as the Tunnel Type
-
Follow the setup steps. In the Install section, select Docker and save the Token that appears.
-
Then, add a Public Hostname. This is the hostname for exposing services. Suppose you have an AList service on the mini PC using port 5244. You want
alist.example.comto access it:Subdomain: alist Domain: example.com Type: HTTP URL: localhost:5244But if it’s Docker-deployed (recommended), use container names:
Subdomain: alist Domain: example.com Type: HTTP URL: alist:5244 -
Now the cloud setup is done. Let’s configure the mini PC:
Mini PC
-
Create a new folder called
cloudflared, placed alongside other services:~ βββ cloudflared β βββ docker-compose.yml βββ service1 β βββ docker-compose.yml β βββ volume βββ service2 βββ docker-compose.yml βββ volume -
Create
docker-compose.yml. Fill in the token from earlier. You don’t even need to mount local volumes:services: cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared restart: unless-stopped command: tunnel --no-autoupdate run --token [YOUR TOKEN] networks: - cloudflared networks: cloudflared: external: true -
Create a Docker network named
cloudflared:docker network create cloudflared -
That’s it! When adding services, just create new domains in Cloudflared.
Other Methods
There are other protocols I won’t try in detail. The principles are similar to the above two: