← Back to all posts
SecurityDockerVPSUFWDevOpsTugShield

Hardening Your Docker VPS: Essential Security Practices for Production

tug.sh Team

tug.sh Team

Core Team

4 min read

When you provision a fresh Ubuntu or Debian VPS and install Docker, you have an exceptionally powerful deployment platform. But you also have a major security concern if default configurations remain untouched.

Many developers are surprised to discover that Docker automatically modifies your Linux firewall (iptables) to expose container ports directly to the public internet, completely bypassing standard ufw (Uncomplicated Firewall) rules.

If you bind a container with -p 5432:5432 or -p 6379:6379, your internal database or Redis cache is often open to the entire world, even if you thought your firewall was enabled.

Here are the essential security practices required to harden a production Docker VPS.


1. The Docker & UFW Bypass Problem (And How to Fix It)

When UFW is enabled, you might run:

sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

You might assume that port 5432 or 8080 is blocked. It is not.

Docker injects its own routing chains into iptables before the UFW input filter. When external packets hit your server on an exposed container port, Docker forwards them immediately to the container bridge before UFW ever inspects them.

Fix 1: Bind Containers Exclusively to Localhost

Whenever exposing internal services that shouldn't be publicly reachable (like databases, queues, or admin panels):

# docker-compose.yml
services:
  database:
    image: postgres:16-alpine
    ports:
      # Good: Binds ONLY to local loopback interface
      - "127.0.0.1:5432:5432"
      
      # Dangerous: Binds to 0.0.0.0 (public internet!)
      # - "5432:5432"

Fix 2: Use Docker Networks Instead of Exposing Ports

If two containers need to communicate (e.g. your Next.js frontend and your PostgreSQL database), they do not need to bind host ports at all. Put them on the same user-defined Docker bridge network:

networks:
  internal:
    driver: bridge

services:
  web:
    image: my-app:latest
    networks:
      - internal
    ports:
      - "80:3000"

  db:
    image: postgres:16-alpine
    networks:
      - internal
    # Notice: No host port bindings needed!

2. Lock Down SSH Access

Brute-force SSH attacks begin within seconds of provisioning any public IP address. Protect your SSH gateway with these baseline settings:

Edit /etc/ssh/sshd_config:

# Disable password authentication completely (keys only)
PasswordAuthentication no

# Disable direct root login
PermitRootLogin prohibit-password

# Optional: Change the default port from 22 to reduce automated scanner noise
Port 2222

After making changes, verify your configuration and restart the SSH service:

sudo sshd -t && sudo systemctl restart ssh

3. Run Containers as Non-Root Users

By default, processes running inside a container run as the root user (UID 0). While Linux namespaces isolate the container filesystem, a container breakout vulnerability gives the attacker immediate root permissions on your host kernel.

Always declare a non-root user in your Dockerfile or compose service:

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["npm", "start"]

Or in Docker Compose:

services:
  worker:
    image: my-worker:latest
    user: "1000:1000"

4. Enable Automatic Security Updates

Outdated system packages are low-hanging fruit for automated exploit kits. Enable unattended-upgrades on Debian/Ubuntu so security patches are applied automatically without rebooting:

sudo apt update && sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

5. Automated VPS Auditing with TugShield

Keeping track of open ports, firewall leaks, and container vulnerabilities manually across multiple VPS servers is tedious and easily neglected.

With TugShield built into tug.sh:

  • Continuous Auditing in Heartbeats: The lightweight tug-agent audits your system firewall, open TCP/UDP listeners, and SSH configuration every 15 seconds.
  • 1-Click UFW Orchestration: Manage firewall rules directly from your dashboard without terminal mistakes.
  • Intrusion Alerts: Get instant notifications if a container opens an unexpected public port or experiences repeated crash loops.

Summary Checklist for Production VPS Security

  • Fix Docker port exposures: Bind internal ports to 127.0.0.1 or rely strictly on internal Docker networks.
  • Enforce SSH keys: Disable password authentication and root passwords.
  • Least Privilege: Run container application processes with unprivileged UIDs.
  • Automated Updates: Enable unattended security updates for your Linux distribution.
  • Continuous Telemetry: Monitor container resource limits and security posture with tools like TugShield.