To access a local web server behind NAT from inside a Docker container, you must run the SSH client within the container itself. This approach allows the container to establish an outbound tunnel to a public VPS, which then forwards traffic back to your private network. By embedding the SSH command in the container's entrypoint, you ensure the tunnel is part of the application lifecycle.
The Core Problem: Host vs. Container Networking
When developers attempt to solve NAT traversal issues, they often place the SSH tunneling logic on the host machine. While this works for simple cases, it fails when the application running inside the container needs direct, low-latency access to resources that are only reachable via the tunnel. If the tunnel exists only on the host, the container cannot route its internal requests through that secure channel because the container has its own isolated network namespace. The solution requires shifting the responsibility of maintaining the connection from the host operating system to the container runtime. The container must act as both the client establishing the tunnel and the service consuming the tunneled connection. This ensures that any process inside the container can communicate with the remote endpoint as if it were locally bound, bypassing the NAT restrictions entirely. For more details on general connectivity challenges in this setup, see Reaching Different Docker Containers Behind NAT.Prerequisites and Architecture
Before configuring the container, you need three components: 1. A public Virtual Private Server (VPS) with a static IP address. 2. An SSH key pair generated specifically for this purpose, without a passphrase. 3. The target local web server running on your private network. The architecture works as follows: Your local machine runs a web server on port 8080. You generate an SSH key and add the public key to theauthorized_keys file on your VPS. Then, you configure a Docker container that initiates an SSH reverse tunnel to the VPS. The VPS listens on a specific port (e.g., 2222) and forwards all incoming traffic into the tunnel, which delivers it to your local web server.
Creating the Dockerfile
A common mistake is using invalid CMD syntax or failing to handle SSH keys correctly. The following Dockerfile uses Alpine Linux for a small footprint and includes the necessary OpenSSH client. Crucially, it copies the private key into the image and sets the correct permissions.FROM alpine:latest
# Install openssh-client
RUN apk add --no-cache openssh-client
# Copy the SSH private key
# Ensure you use a key without a passphrase for automation
COPY id_rsa /root/.ssh/id_rsa
# Set strict permissions for the key
RUN chmod 600 /root/.ssh/id_rsa && \
mkdir -p /root/.ssh && \
echo "StrictHostKeyChecking no" >> /root/.ssh/config
# Command to start the tunnel and keep the container alive
CMD ["sh", "-c", "ssh -f -N -R 2222:localhost:8080 user@your-vps-ip -o ServerAliveInterval=60"]
In this configuration, the -f flag sends SSH to the background after authentication. The -N flag tells SSH not to execute a remote command, as we only want to forward ports. The -R 2222:localhost:8080 directive maps port 2222 on the VPS to port 8080 on the local machine (from the perspective of the SSH client, which is the container).
Note that relying on a static key in the image is acceptable for development but should be handled via environment variables or secrets management in production. For information on managing connections reliably, check out Automatically Restart SSH Tunnel for Seamless Connectivity in Coffee Shops and Beyond.
Building and Running the Container
Once the Dockerfile is ready, build the image by providing the context directory whereid_rsa resides.
docker build -t ssh-tunnel-container .
Run the container with the appropriate environment variables if you choose to inject the IP address dynamically, though hardcoding it in the CMD is simpler for testing.
docker run -d --name nat-tunnel ssh-tunnel-container
If the container exits immediately, check the logs using docker logs nat-tunnel. Common errors include permission denied on the key (ensure chmod 600) or authentication failures (verify the public key is in the VPS authorized_keys).
Verifying the Connection
To verify the tunnel is active, you can test the connection from another machine or device on the internet. Access the VPS IP address on port 2222.curl http://your-vps-ip:2222
If configured correctly, this request will traverse the SSH tunnel and return the response from your local web server running on port 8080. This confirms that the NAT traversal is successful and the container is properly forwarding traffic.
Troubleshooting Common Issues
* Connection Refused: Ensure the VPS firewall allows inbound TCP traffic on port 2222. * Permission Denied: Double-check that the private key has no passphrase and thatStrictHostKeyChecking is disabled in the SSH config to prevent interactive prompts.
* Container Crashes: If the SSH session drops, the container will exit. Use a supervisor like supervisord or a wrapper script with retry logic to maintain the tunnel. See Closing SSH Connection with Keygen for insights on key management.
Frequently Asked Questions
Can I use password authentication instead of keys?
No, automated tunnels require key-based authentication because there is no terminal to enter a password. Passwords introduce security risks and break non-interactive scripts.
How do I secure the SSH private key in the Docker image?
Avoid baking keys directly into images. Instead, mount the key as a volume at runtime or use Docker Secrets to inject the key securely during the build or run phase.
What happens if the internet connection drops?
The SSH tunnel will break, and the container may exit. Implementing a health check or a wrapper script that restarts the SSH client upon failure ensures continuous availability.
Is this method suitable for production environments?
While functional, dedicated tools like Cloudflare Tunnel or Ngrok are often preferred for production due to better reliability, encryption standards, and ease of management compared to raw SSH tunnels.
Why does my container exit immediately after starting?
Docker containers stop when the main process finishes. If the SSH command fails or completes instantly, the container exits. Ensure the SSH command runs in the foreground or uses flags like -f combined with a sleep loop to keep the process alive.