How to Install Docker and PostgreSQL Securely on a Linux Server (Production-Friendly Guide)
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) withsudoprivileges - SSH key‑based login
- Firewall enabled (UFW)
Important: Do not use
rootfor 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
dockergroup. 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: truekeeps 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
pgdatavolume. - 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
| Setting | Value |
|---|---|
| Host | 127.0.0.1 |
| Port | 5432 |
| User | appuser |
| Password | your password |
| Database | appdb |
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
pgdatavolume. - Add resource limits to the PostgreSQL container.
- Use
docker-composefor 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 🚀