How to Install Docker and PostgreSQL Securely on a Linux Server (Production-Friendly Guide)

Published: (December 15, 2025 at 04:50 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Running databases on servers is easy, but securing them is where most setups fail. This guide shows how to:

  • Install Docker the right way
  • Lock down Docker so it doesn’t weaken your server
  • Run PostgreSQL in Docker without exposing it to the internet
  • Access the database securely from your local machine
  • Avoid common security foot‑guns

Ideal for single servers, side projects, SaaS MVPs, and production environments that value safety over shortcuts.

Prerequisites

  • Ubuntu 22.04 or 24.04
  • A non‑root admin user (e.g., dev) with sudo privileges
  • SSH key‑based login
  • Firewall enabled (UFW)

Important: Do not use root for daily work.

Install Docker from the Official Repository

# Remove any old Docker packages
sudo apt remove -y docker docker-engine docker.io containerd runc

# Install prerequisites
sudo apt update
sudo apt install -y ca-certificates curl gnupg

# Set up Docker’s official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add Docker’s APT repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt update
sudo apt install -y \
  docker-ce \
  docker-ce-cli \
  containerd.io \
  docker-buildx-plugin \
  docker-compose-plugin

Verify the installation:

sudo docker version
sudo docker run --rm hello-world

If you see “Hello from Docker!”, Docker is working.

Add Your User to the docker Group

sudo usermod -aG docker dev

Log out and back in (or ssh dev@SERVER_IP) and verify:

docker ps

⚠️ Warning: Do not add application or service users to the docker group. The group grants root‑equivalent access.

Harden Docker Daemon

Create or edit /etc/docker/daemon.json:

sudo nano /etc/docker/daemon.json

Paste the following configuration:

{
  "icc": false,
  "live-restore": true,
  "no-new-privileges": true,
  "userns-remap": "default",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Explanation

  • icc: false – prevents containers from communicating with each other.
  • userns-remap: "default" – maps container root to an unprivileged host user.
  • no-new-privileges: true – blocks privilege escalation.
  • Log options limit disk usage.
  • live-restore: true keeps containers running during Docker restarts.

Restart Docker and verify the user‑namespace remapping:

sudo systemctl restart docker
docker info | grep -i userns

Prepare a Docker Volume for PostgreSQL

docker volume create pgdata

Run PostgreSQL 18 in Docker

Note: PostgreSQL 18 stores data in /var/lib/postgresql. Do not mount /var/lib/postgresql/data.

docker run -d \
  --name postgres \
  --restart unless-stopped \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_PASSWORD=STRONG_PASSWORD_HERE \
  -e POSTGRES_DB=appdb \
  -v pgdata:/var/lib/postgresql \
  -p 127.0.0.1:5432:5432 \
  postgres:18
  • The database port is bound only to 127.0.0.1, so it is not publicly exposed.
  • Data persists in the pgdata volume.
  • PostgreSQL runs as a non‑root user inside the container.

Verify the Container

docker ps
docker logs postgres

You should see a line such as:

database system is ready to accept connections

Enter the database:

docker exec -it postgres psql -U appuser -d appdb

Inside psql:

SELECT version();
\q

Ensure the Port Is Not Exposed Publicly

ss -tulpn | grep 5432

Expected output:

127.0.0.1:5432

If you see 0.0.0.0:5432, stop the container and fix the binding.

Check the firewall:

sudo ufw status

PostgreSQL should not appear in the list of allowed services.

Secure Remote Access via SSH Tunnel

From your local machine, create an SSH tunnel that forwards the remote PostgreSQL port to your local machine:

ssh -N -L 5432:127.0.0.1:5432 dev@SERVER_IP

Leave this terminal open while you work.

Connect with Your Preferred Client

SettingValue
Host127.0.0.1
Port5432
Userappuser
Passwordyour password
Databaseappdb

Works with TablePlus, DBeaver, pgAdmin, psql, etc.

Summary

  • Secure Docker installation using the official repository and hardened daemon defaults.
  • PostgreSQL 18 runs in a container with a future‑safe layout and no public port exposure.
  • SSH tunneling provides encrypted, private access to the database.

You don’t need Kubernetes or managed services to be secure—discipline and correct defaults are enough.

Next Steps (optional)

  • Set up automated backups for the pgdata volume.
  • Add resource limits to the PostgreSQL container.
  • Use docker-compose for reproducible deployments.
  • Monitor disk usage and periodically audit exposed ports.

If you follow this guide, your server will be more secure than most production setups. Happy shipping 🚀

Back to Blog

Related posts

Read more »