ThisCalculator

Everyday

Securely Expose Docker Services to the Internet

Updated · Standard formula, shown below

This guide walks you through safely publishing a Docker container so it’s reachable from the internet, without opening your whole host to attack. You’ll learn how to set up a reverse proxy with TLS, restrict access, and keep your services patched and monitored.

What You’ll Need

  • A Linux server (Ubuntu/Debian recommended) with Docker and Docker Compose installed.
  • A domain name pointing to your server’s public IP (A record).
  • Ports 80 and 443 open in your firewall/router (forwarded to the server).
  • Basic familiarity with the command line and editing files.

Step-by-Step

  1. Create a dedicated network for your proxy and services. Run: docker network create proxy. This isolates traffic and lets containers communicate by name.
  2. Set up a reverse proxy (Traefik or Nginx Proxy Manager). For this guide, we’ll use Traefik v2. Create a directory traefik and a docker-compose.yml with the Traefik service, mounting the Docker socket (read-only) and a config file. Define entrypoints for HTTP and HTTPS, and enable the Docker provider.
  3. Configure automatic TLS certificates. In Traefik’s static config, add a certificate resolver using Let’s Encrypt, with a file for storing certs and an email for notifications. Set HTTP to redirect to HTTPS.
  4. Expose your first service. For each container you want public, attach it to the proxy network and add Docker labels (if using Traefik) or configure a host rule. Example labels: traefik.enable=true, traefik.http.routers.myapp.rule=Host("app.example.com"), traefik.http.routers.myapp.tls.certresolver=letsencrypt.
  5. Do not publish ports directly. Remove any ports: from your service definitions. Only the proxy should bind to ports 80/443 on the host. This prevents accidental exposure of other ports.
  6. Restrict access with firewall rules. Use UFW or iptables to allow only ports 80, 443, and SSH (port 22). Block all other inbound traffic. For example: sudo ufw allow 22,80,443/tcp and sudo ufw enable.
  7. Add authentication for sensitive apps. If your service has no built-in login, add HTTP Basic Auth via Traefik middleware. Generate a password with htpasswd and configure a middleware in Traefik’s dynamic config.
  8. Keep Docker and images updated. Regularly run docker compose pull and docker compose up -d to fetch security patches. Consider using Watchtower for automatic updates (with caution).
  9. Monitor logs and resource usage. Check docker logs [container] for errors, and use docker stats to see CPU/memory. Set up simple uptime monitoring (e.g., UptimeRobot) to alert you if a service goes down.
  10. Test your setup. Visit https://app.example.com in a browser. Confirm the padlock icon shows a valid certificate. Also try http://app.example.com to verify it redirects to HTTPS.

Common Mistakes

  • Publishing container ports (e.g., ports: "8080:80") in addition to using a proxy. This bypasses your proxy’s protection and exposes the service directly.
  • Forgetting to update the Docker socket mount to read-only. Traefik needs access to discover containers, but it should never write to the socket.
  • Using a weak or default password for admin panels. Always change default credentials and use strong passwords.
  • Opening unnecessary ports on the host firewall, like 2375 (Docker API) or 3306 (MySQL). Only 80, 443, and SSH should be open.
  • Ignoring TLS certificate renewal failures. Check that your domain resolves correctly and that port 80 is reachable for the ACME challenge.

Tips

  • Use a separate Docker network for each service’s internal dependencies (e.g., database) and only attach the app itself to the proxy network.
  • Set resource limits on containers to prevent a single service from exhausting host memory or CPU.
  • Enable Docker’s built-in logging rotation to avoid filling your disk with logs.
  • For extra security, run the proxy in a separate VM or use a cloud load balancer in front of it.
  • Regularly audit which containers are attached to the proxy network and remove any that don’t need to be public.

Troubleshooting

  • Certificate not issued: Check that your domain’s A record points to your server’s public IP. Verify port 80 is reachable from outside (use a tool like canyouseeme.org). Ensure no other process is using port 80.
  • 502 Bad Gateway from proxy: The proxy can’t reach your app. Confirm the app container is running (docker ps) and that it’s on the same network as the proxy. Check the app’s internal port matches the proxy’s load balancer configuration.
  • Service not accessible at all: Check the host firewall (sudo ufw status) and cloud provider security groups. Also test locally with curl -H "Host: app.example.com" http://localhost to see if the proxy is working.
  • Redirect loop or mixed content: Ensure your app is configured to trust the proxy’s forwarded headers (X-Forwarded-Proto). Set environment variables like VIRTUAL_HOST or TRUST_PROXY if your app supports them.
  • Container restarts unexpectedly: Check docker logs for crash reasons. Often it’s a misconfigured environment variable or a missing volume. Use docker inspect to see exit codes.

Frequently Asked Questions

Can I expose a service without a domain name?

Yes, but you’ll need to use the server’s IP address with a self-signed certificate, or use a dynamic DNS service. For production, a domain is strongly recommended for trusted TLS.

Is it safe to expose the Docker API socket?

No. Never expose the Docker socket directly to the internet. Use a reverse proxy with authentication if you must access it remotely, or better, use a tool like Portainer with its own authentication.

How do I update a running container without downtime?

Use docker compose up -d after pulling new images. Compose will recreate containers with zero downtime if you have multiple replicas or use rolling updates. For single containers, there’s a brief interruption.

What if my service uses WebSockets?

Most reverse proxies support WebSockets by default. With Traefik, ensure you don’t strip the Upgrade headers. Nginx Proxy Manager also handles it automatically.

Should I use a VPN instead of exposing to the internet?

For personal or internal tools, a VPN (like WireGuard) is more secure. Only expose services that genuinely need public access, and always put them behind a proxy with TLS and authentication.

How do I revoke access to a service?

Remove the container from the proxy network (docker network disconnect proxy [container]) or stop the container. Also update your firewall to block the domain if needed.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.