🐳 Production Home Server
Docker + Nginx + ngrok Architecture Guide
Transforming your laptop into a scalable multi-project server
🌐 Your Public URL: [Link]
🏗 Architecture Overview
Your new production setup routes all public traffic through a single ngrok tunnel → Nginx →
Docker containers. Each project runs in its own isolated container, accessible via URL sub-
paths.
How Traffic Flows
Internet
|
▼
ngrok (public HTTPS tunnel)
|
▼
Nginx (port 80 — reverse proxy + router)
|
├── /api/ ──▶ fastapi-app container (port 8000)
├── /django/ ──▶ django-app container (port 8001)
├── /project3/ ──▶ project3-app container (port 8002)
└── / ──▶ dashboard container (port 3000)
💡 Key Benefit: Each project is completely isolated. You can start, stop, rebuild, or update one
project without touching any other.
📁 Folder Structure
Create this exact folder structure on your server. All projects live under /srv/projects.
/srv/projects/
├── [Link] # Master file — runs everything
├── nginx/
│ ├── [Link] # Main Nginx config
│ └── conf.d/
│ ├── [Link] # FastAPI routing rules
│ ├── [Link] # Django routing rules
│ └── [Link] # Dashboard routing rules
├── fastapi-app/
│ ├── Dockerfile
│ ├── [Link]
│ └── [Link]
├── django-app/
│ ├── Dockerfile
│ ├── [Link]
│ ├── [Link]
│ └── myproject/
│ ├── [Link]
│ ├── [Link]
│ └── [Link]
├── dashboard/ # Optional landing page
│ ├── Dockerfile
│ └── [Link]
└── shared/
└── .env # Shared environment variables
Create the structure with this single command:
mkdir -p /srv/projects/{nginx/conf.d,fastapi-app,django-app,dashboard,shared}
🐳 Step 1 — Install Docker & Docker Compose
Install Docker Engine
# Remove old versions
sudo apt remove docker docker-engine [Link] containerd runc -y
# Install dependencies
sudo apt update
sudo apt install ca-certificates curl gnupg lsb-release -y
# Add Docker's official GPG key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL [Link] | sudo gpg --dearmor -o
/etc/apt/keyrings/[Link]
# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture)
signed-by=/etc/apt/keyrings/[Link]] \
[Link] $(lsb_release -cs) stable" | \
sudo tee /etc/apt/[Link].d/[Link] > /dev/null
# Install Docker
sudo apt update
sudo apt install docker-ce docker-ce-cli [Link] docker-compose-plugin -y
# Allow mirshad to run Docker without sudo
sudo usermod -aG docker mirshad
newgrp docker
# Verify
docker --version
docker compose version
📋 Step 2 — Master [Link]
This single file defines your entire infrastructure. Save it at /srv/projects/[Link]
# /srv/projects/[Link]
version: '3.9'
networks:
server-net: # All containers talk on this private network
driver: bridge
services:
# ── Nginx Reverse Proxy ────────────────────────────────────────
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- '80:80' # Only Nginx is exposed to host
volumes:
- ./nginx/[Link]:/etc/nginx/[Link]:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
networks:
- server-net
restart: always
depends_on:
- fastapi-app
- django-app
- dashboard
# ── FastAPI Project ─────────────────────────────────────────────
fastapi-app:
build: ./fastapi-app
container_name: fastapi-app
expose:
- '8000' # Only visible inside server-net, not the host
env_file:
- ./shared/.env
networks:
- server-net
restart: always
volumes:
- ./fastapi-app:/app # Live reload during dev
# ── Django Project ──────────────────────────────────────────────
django-app:
build: ./django-app
container_name: django-app
expose:
- '8001'
env_file:
- ./shared/.env
networks:
- server-net
restart: always
volumes:
- ./django-app:/app
# ── Optional Dashboard ──────────────────────────────────────────
dashboard:
build: ./dashboard
container_name: dashboard
expose:
- '3000'
networks:
- server-net
restart: always
# ── PostgreSQL Database ─────────────────────────────────────────
postgres:
image: postgres:15-alpine
container_name: postgres-db
expose:
- '5432'
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: strongpassword123
POSTGRES_DB: maindb
volumes:
- postgres_data:/var/lib/postgresql/data # Data persists on disk
networks:
- server-net
restart: always
volumes:
postgres_data: # Named volume — survives container restarts
⚙ Step 3 — Nginx Configuration
Main Nginx Config /srv/projects/nginx/[Link]
# /srv/projects/nginx/[Link]
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/[Link];
default_type application/octet-stream;
# Security headers
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
# Logging
access_log /var/log/nginx/[Link];
error_log /var/log/nginx/[Link];
# Load all project configs
include /etc/nginx/conf.d/*.conf;
}
FastAPI Routing /srv/projects/nginx/conf.d/[Link]
# /srv/projects/nginx/conf.d/[Link]
server {
listen 80;
server_name _;
# FastAPI project at /api/
location /api/ {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Django project at /django/
location /django/ {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Dashboard at root /
location / {
proxy_pass [Link]
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
📦 Step 4 — Project Dockerfiles
FastAPI /srv/projects/fastapi-app/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "[Link]", "--port", "8000", "--reload"]
FastAPI /srv/projects/fastapi-app/[Link]
fastapi
uvicorn[standard]
psycopg2-binary
python-dotenv
Django /srv/projects/django-app/Dockerfile
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y libpq-dev gcc && rm -rf
/var/lib/apt/lists/*
COPY [Link] .
RUN pip install --no-cache-dir -r [Link]
COPY . .
EXPOSE 8001
CMD ["python", "[Link]", "runserver", "[Link]:8001"]
Django /srv/projects/django-app/[Link]
django>=4.2
psycopg2-binary
gunicorn
python-dotenv
djangorestframework
🔒 Production Note: For real production use, replace runserver with gunicorn: gunicorn
[Link]:application --bind [Link]:8001 --workers 2
🔑 Step 5 — Shared Environment Variables
Save secrets in one place. All containers share this file.
/srv/projects/shared/.env
# Database
DB_HOST=postgres
DB_PORT=5432
DB_NAME=maindb
DB_USER=admin
DB_PASSWORD=strongpassword123
# Django
DJANGO_SECRET_KEY=your-very-secret-key-here
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=[Link],localhost
# Public URL
PUBLIC_URL=[Link]
⚠️Security: Never commit the .env file to Git. Add it to .gitignore immediately.
🌐 Step 6 — Update ngrok Systemd Service
Your existing ngrok setup works fine. Just make sure it still points to port 80 (Nginx), which it
already does. No changes needed — ngrok tunnels to Nginx, which now routes to Docker
containers.
✅ Existing ngrok config is correct: addr: 80 — This points to Nginx, which distributes to all Docker
containers.
🚀 Step 7 — Launch Everything
First-time Launch
cd /srv/projects
# Build all images and start in background
docker compose up -d --build
# Watch logs
docker compose logs -f
Verify Everything is Running
# See all running containers
docker compose ps
# Test FastAPI internally
curl [Link]
# Test Django internally
curl [Link]
# Test via public URL
curl [Link]
⚡ Daily Operations Cheatsheet
Command What it does
docker compose ps Show all containers + status
docker compose logs -f Stream all container logs
docker compose logs fastapi-app -f Stream one project's logs
docker compose up -d --build fastapi-app Rebuild & restart one project only
docker compose restart nginx Reload Nginx config
docker compose stop django-app Stop one project
docker compose start django-app Start one project
docker compose down Stop all containers
docker compose down -v Stop all + delete volumes
docker exec -it fastapi-app bash Shell into a container
docker stats Live CPU/RAM usage
docker system prune -f Clean up unused images/containers
➕ Adding a New Project (Template)
Follow these 4 steps every time you add a new backend project:
Step A — Create project folder + Dockerfile
mkdir -p /srv/projects/my-new-project
# Add your code and Dockerfile (copy from fastapi-app template)
Step B — Add to [Link]
my-new-project:
build: ./my-new-project
container_name: my-new-project
expose:
- '8002'
env_file:
- ./shared/.env
networks:
- server-net
restart: always
Step C — Add Nginx routing rule
# In /srv/projects/nginx/conf.d/[Link]
# Add inside the server {} block:
location /newproject/ {
proxy_pass [Link]
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
Step D — Deploy
cd /srv/projects
docker compose up -d --build my-new-project
docker compose restart nginx
# Your new project is live at:
# [Link]
🔒 Security Hardening
These steps protect your laptop server from unwanted access.
Firewall — Allow Only What's Needed
sudo ufw enable
sudo ufw allow ssh # SSH remote management
sudo ufw allow 80 # Nginx (ngrok connects here)
sudo ufw deny 5432 # Block direct Postgres access from outside
sudo ufw deny 8000 # Block direct FastAPI access from outside
sudo ufw deny 8001 # Block direct Django access from outside
sudo ufw status
Keep System + Docker Updated
sudo apt update && sudo apt upgrade -y
docker compose pull # Pull latest base images
docker compose up -d --build # Rebuild with latest images
Monitor Resource Usage
# Real-time container stats
docker stats
# Disk usage
docker system df
# Cleanup old images
docker image prune -f
🔧 Troubleshooting
Problem Fix
502 Bad Gateway Container crashed. Run: docker compose logs
<name> -f
Container keeps restarting Check logs: docker compose logs <name> --
tail=50
Nginx config error docker compose exec nginx nginx -t
Port conflict on host Check: sudo lsof -i :80 — stop conflicting
service
Database connection refused Make sure container name matches DB_HOST
in .env
Changes not taking effect Rebuild: docker compose up -d --build <name>
Disk full Run: docker system prune -f && docker volume
prune -f
🔄 Auto-Start on Laptop Boot
Create a systemd service so Docker Compose starts automatically when your laptop turns on.
Create the Service File
sudo nano /etc/systemd/system/[Link]
[Unit]
Description=Docker Compose Projects
After=[Link] [Link]
Requires=[Link]
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/projects
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
User=mirshad
[Install]
WantedBy=[Link]
Enable It
sudo systemctl daemon-reload
sudo systemctl enable [Link]
sudo systemctl start [Link]
✅ Your Complete Stack Summary
Component Role
ngrok Public HTTPS tunnel
Nginx (Docker) Reverse proxy + router
FastAPI (Docker) Your FastAPI backends
Django (Docker) Your Django backends
PostgreSQL (Docker) Shared database
systemd services Auto-restart on boot
🎉 Your server is production-ready! Every project is isolated in Docker, Nginx routes traffic cleanly,
ngrok gives you a permanent public URL, and everything restarts automatically on reboot.