How to Securely Host Containers on a VPS with Nginx Proxy Manager and SSL
When deploying multiple containers on your VPS, security should be your top priority. A crucial part of this is properly managing your domain and subdomain routing.
While it’s possible to manually install Nginx and Certbot for SSL certificate generation, there’s a more user-friendly solution: use a containerized version of Nginx Proxy Manager (NPM) with a built-in dashboard and PostgreSQL for data storage.
Key Setup Components:
- Nginx as a reverse proxy
- PostgreSQL for configuration data
- .env file to manage environment variables securely
Create folder, cd into it and create two files docker-compose.yml and .env.
# docker-compose.yml
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
# These ports are in format <host-port>:<container-port>
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
# Add any other Stream port you want to expose
# - '21:21' # FTP
environment:
# Postgres parameters:
DB_POSTGRES_HOST: 'db'
DB_POSTGRES_PORT: '5432'
DB_POSTGRES_USER: ${POSTGRES_USER}
DB_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
DB_POSTGRES_NAME: ${POSTGRES_DB}
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL}
INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD}
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
depends_on:
- db
db:
image: postgres:latest
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./postgres:/var/lib/postgresql/data
# .env
POSTGRES_USER=db_user
POSTGRES_PASSWORD=db_password
POSTGRES_DB=db_name
INITIAL_ADMIN_EMAIL=admin_email
INITIAL_ADMIN_PASSWORD=strong_admin_password
Here is how to start/stop service:
# start in detached mode
docker compose up -d
# stop service
docker compose down
To access to your dashboard start service and visit ip address of your VPS on port 81, enter login/password from .env file. Almost done. But accessing it without SSL have no sense.
Point your domain name to your IP address and generate SSL certificate for your domain zone (wildcard) *.domain.com. This will allow you to use one certificate for all subdomain. But the trick is you need to use DNS challenge to achieve it – dns_digitalocean_token.
Then create subdomain particular for nginx proxy manager (NPM), for example nginx-proxy-manager.domain.com and create a proxy host in NPM and point it to http://localhost:81.
After completing these steps stop your service:
docker compose down
edit docker-compose.yml file and comment line with port 81.
# - '81:81' # Admin Web Port
Save file and start service again. Now it should be securely accessible by you subdomain address.