This guide provides a one-click Docker deployment for PrivyDrop. It supports both private and public networks, automates config/build/start, and provisions HTTPS certificates.
# Private LAN (no domain/public IP)
bash ./deploy.sh --mode lan-http
# Private LAN + TURN (for complex NAT/LAN)
bash ./deploy.sh --mode lan-http --with-turn
# LAN HTTPS (self-signed; dev/managed env; explicitly enable 8443)
bash ./deploy.sh --mode lan-tls --enable-web-https --with-nginx
# Public IP without domain (with TURN; recommended with Nginx for same-origin)
bash ./deploy.sh --mode public --with-turn --with-nginx
# Public domain (HTTPS + Nginx + TURN + SNI 443, auto-issue/renew certs)
bash ./deploy.sh --mode full --domain your-domain.com --with-nginx --with-turn --le-email you@domain.com- Requires Docker Compose v2 (command
docker compose). - In full mode, Letβs Encrypt (webroot) is auto-issued and auto-renewed (no downtime); SNI 443 multiplexing is enabled by default (
turn.your-domain.comβ coturn:5349; others β web:8443).
- lan-http: Intranet HTTP; fastest to start; no TLS
- lan-tls: Intranet HTTPS (self-signed; dev/managed env); 8443 disabled by default; enable via
--enable-web-https; HSTS disabled; turns:443 not guaranteed - public: Public HTTP + TURN; works without a domain (no HTTPS/turns:443)
- full: Domain + HTTPS (Letβs Encrypt auto-issue/renew) + TURN; SNI 443 split enabled by default (use
--no-sni443to disable)
Compared to traditional deployment methods, Docker deployment offers the following advantages:
| Comparison | Traditional Deployment | Docker Deployment |
|---|---|---|
| Deploy Time | 30-60 minutes | 5 minutes |
| Technical Requirements | Linux ops experience | Basic Docker knowledge |
| Environment Requirements | Public IP + Domain | Works on private networks |
| Configuration Complexity | 10+ manual steps | One-click auto configuration |
| Success Rate | ~70% | >95% |
| Maintenance Difficulty | Manual multi-service management | Automatic container management |
- CPU: 1 core
- Memory: 512MB
- Disk: 2GB available space
- Network: Any network environment (private/public)
- CPU: 2+ cores
- Memory: 1GB+
- Disk: 5GB+ available space
- Network: 100Mbps+
- The frontend Docker build runs
next build, which can be killed by the kernel on very small hosts. - On fresh 1GBβ2GB servers, add at least 1GB swap before the first production build if memory pressure is high.
- Typical symptom: the frontend image build exits unexpectedly during
next buildor the host shows OOM messages indmesg.
Recommended one-time swap setup on Ubuntu:
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hAfter swap is enabled, rerun the deploy command.
- Docker 20.10+
- Docker Compose 2.x (command
docker compose) - curl (for health checks, optional)
- openssl (cert tools; the script auto-installs certbot)
# Clone the project
git clone https://github.com/david-bai00/PrivyDrop.git
cd PrivyDrop# Always pass an explicit deployment mode
bash ./deploy.sh --mode lan-httpThat's it! π
Use Case: Private network file transfer, personal use, testing environment
bash ./deploy.sh --mode lan-httpFeatures:
- β HTTP access
- β Private network P2P transfer
- β Uses public STUN servers
- β Zero configuration startup
Use Case: Servers with public IP but no domain
bash ./deploy.sh --mode public --with-turn --with-nginxFeatures:
- β HTTP access
- β Built-in TURN server
- β Supports complex network environments
- β Automatic NAT traversal configuration
Use Case: Production environment, public servers with domain
bash ./deploy.sh --mode full --domain your-domain.com --with-nginx --with-turn --le-email you@domain.comFeatures:
- β HTTPS secure access (Letβs Encrypt auto-issue/renew)
- β Nginx reverse proxy
- β Built-in TURN server (default port range 49152-49252/udp)
- β SNI 443 multiplexing (turn. β coturn:5349; others β web:8443)
- β
Same-origin frontend/API gateway by default when
--with-nginxis enabled (NEXT_PUBLIC_API_URLis generated as an empty string, so the browser uses/apiand/socket.io/) - β
Production CORS generation covers the canonical domain and its
wwwvariant by default (for examplehttps://example.com,https://www.example.com) - β Complete production setup
Tip: The script no longer auto-detects the deployment mode; always pass
--mode lan-http|lan-tls|public|full. If the detected LAN IP is not the one you expect, add--local-ip 192.168.x.xto override.
# Modify .env file
FRONTEND_PORT=8080
BACKEND_PORT=8081
HTTP_PORT=8000Set the following variables in .env (or export them before running deploy.sh) when the build needs to go through a proxy. The configuration generator now preserves these fields on subsequent runs.
HTTP_PROXY=http://your-proxy:7890
HTTPS_PROXY=http://your-proxy:7890
NO_PROXY=localhost,127.0.0.1,backend,frontend,redis,coturndocker compose passes these values as build args; the Dockerfiles expose them as environment variables so npm/pnpm automatically reuse the proxy. Leave them blank if you don't need a proxy.
# Always include an explicit --mode (examples)
bash ./deploy.sh --mode lan-http --with-nginx
bash ./deploy.sh --mode lan-http --with-turn
bash ./deploy.sh --mode public --with-turn --with-nginx
bash ./deploy.sh --mode full --domain your-domain.com --with-nginx --with-turn --with-sni443 --le-email you@domain.com
# Adjust TURN port range (default 49152-49252/udp)
bash ./deploy.sh --mode full --domain your-domain.com --with-nginx --with-turn --le-email you@domain.com --turn-port-range 55000-55100-
With Nginx (recommended, same-origin gateway)
- lan-http/public:
http://localhost(orhttp://<public IP>) - lan-tls (with
--enable-web-https):https://localhost:8443(orhttps://<LAN IP>:8443) - full (with domain):
https://<your-domain>(443) - Health checks:
curl -fsS http://localhost/api/health(lan-http/public),curl -kfsS https://localhost:8443/api/health(lan-tls+https),curl -fsS https://<domain>/api/health(full)
- lan-http/public:
-
Without Nginx (direct ports, for debugging only)
- Frontend:
http://localhost:3002(orhttp://<LAN IP>:3002) - API:
http://localhost:3001(orhttp://<LAN IP>:3001) - Note: direct ports may cause CORS or 404 in production/public setups and are not recommended for public access.
- Frontend:
- lan-tls: with
--enable-web-https, access viahttps://localhost:8443(certs indocker/ssl/). Importdocker/ssl/ca-cert.peminto your browser or trust store on first use. - full: after Letβs Encrypt issuance, access via
https://<your-domain>(443). Certs auto-issue/renew; the deploy hook hot-reloads edge services on renewal, and the initial full-mode deploy also force-recreatesnginx(andcoturnwhen enabled) to guarantee the new HTTPS/SNI config is active.
docker compose ps# View all service logs
docker compose logs -f
# View specific service logs
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f redis# Restart all services
docker compose restart
# Restart specific service
docker compose restart backend# Stop services but keep data
docker compose stop
# Stop services and remove containers
docker compose down# Clean all containers, images and data
bash ./deploy.sh --cleanSymptom: Deployment shows port occupation warning
β οΈ The following ports are already in use: 3002, 3001
Solution:
# First try cleaning previous containers
bash ./deploy.sh --clean # or docker compose down
# If the port is still occupied, locate the process
sudo ss -tulpn | grep :3002
sudo kill -9 <PID>
# Finally, adjust the exposed ports in .env if necessary
vim .env # Update FRONTEND_PORT / BACKEND_PORTSymptom: Containers fail to start or restart frequently
Solution:
# Check memory usage
free -h
# Add swap space (temporary solution)
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfileSymptom: Permission denied errors
Solution:
# Add user to docker group
sudo usermod -aG docker $USER
# Re-login or refresh group permissions
newgrp dockerSymptom: Browser cannot open pages
Solution:
# 1. Check service status
docker compose ps
# 2. Check health status
curl http://localhost:3001/health
curl http://localhost:3002/api/health
# 3. View detailed logs
docker compose logs -f
# 4. Check firewall
sudo ufw statusSymptom: Cannot establish P2P connections
Solution:
# Enable TURN server (re-run with an explicit --mode)
bash ./deploy.sh --mode lan-http --with-turn
# or (public IP, recommended same-origin via Nginx)
bash ./deploy.sh --mode public --with-turn --with-nginx
# Check network connectivity
curl -I http://localhost:3001/api/get_roomThe project provides comprehensive health check functionality:
# Run health check tests
bash test-health-apis.sh
# Manual service checks
curl http://localhost:3001/health # Backend basic check
curl http://localhost:3001/health/detailed # Backend detailed check
curl http://localhost:3002/api/health # Frontend check# View container resource usage
docker stats
# View disk usage
docker system df
# Clean unused resources
docker system prune -f- Enable Nginx Caching:
# Example (public IP, same-origin via Nginx)
bash ./deploy.sh --mode public --with-turn --with-nginx- Configure Resource Limits:
# Add to docker-compose.yml
services:
backend:
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M- Enable Log Rotation:
# Configure log size limits
echo '{"log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"}}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker- Use Dedicated Network:
networks:
privydrop-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16- Enable HTTP/2:
# Auto-enabled (requires HTTPS)
bash ./deploy.sh --mode full --domain your-domain.com --with-nginx --with-turn --le-email you@domain.com- 8443 is disabled by default; explicitly enable with:
bash ./deploy.sh --mode lan-tls --enable-web-https --with-nginx- For development or managed devices only (internal CA trusted fleet-wide); HSTS disabled;
turns:443not guaranteed. For restricted networks (443-only), use full (domain + trusted cert + SNI 443).
Usage (strongly recommended)
- Import the self-signed CA (required)
- Location:
docker/ssl/ca-cert.pem - Browser import:
- Chrome/Edge: Settings β Privacy & Security β Security β Manage certificates β βTrusted Root Certification Authoritiesβ β Import
ca-cert.pem - macOS: Keychain Access β System β Certificates β Import
ca-cert.pemβ set to βAlways Trustβ - Linux (system-wide):
sudo cp docker/ssl/ca-cert.pem /usr/local/share/ca-certificates/privydrop-ca.crtsudo update-ca-certificates
- Chrome/Edge: Settings β Privacy & Security β Security β Manage certificates β βTrusted Root Certification Authoritiesβ β Import
- Without trusting the CA, browser HTTPS will show untrusted cert warnings and API requests will fail.
- Access endpoints (default ports and paths)
- Nginx reverse proxy:
http://localhost - HTTPS (Web):
https://localhost:8443,https://<LAN IP>:8443 - Frontend direct (optional):
http://localhost:3002,http://<LAN IP>:3002 - Note: In lan-tls, 443 is not open; HTTPS uses 8443.
- CORS
- For convenience, common dev origins are allowed by default:
https://<LAN IP>:8443,https://localhost:8443,http://localhost,http://<LAN IP>,http://localhost:3002,http://<LAN IP>:3002. - To minimize allowed origins, edit
CORS_ORIGINin.envand thendocker compose restart backend. - In production,
CORS_ORIGINis a comma-separated list consumed by both Express and Socket.IO. Example:CORS_ORIGIN=https://example.com,https://www.example.com.
- Health checks
curl -kfsS https://localhost:8443/api/healthβ 200bash ./test-health-apis.shβ all tests should pass (frontend container trusts the self-signed CA).
- Deployment hints
- The script prints only reachable Nginx endpoints; in lan-tls it will show
https://localhost:8443(andhttps://<LAN IP>:8443if available).
-
Point your domain A record to the server IP (optional: also
turn.<your-domain>to the same IP)Recommended DNS / CDN layout:
- Web entry: choose one canonical hostname for
--domain(for exampleexample.com) and redirect alternate hostnames such aswww.example.comat the CDN/DNS layer if desired. - TURN entry: keep
turn.<your-domain>as DNS-only when using Cloudflare so TURN traffic does not go through the HTTP proxy. - Current certificate issuance covers
--domainandturn.<your-domain>by default. If you need direct HTTPS on an additional hostname such aswww.<your-domain>, add that certificate handling separately or make it redirect to the canonical host.
- Web entry: choose one canonical hostname for
-
Run:
./deploy.sh --mode full --domain <your-domain> --with-nginx --with-turn --le-email you@domain.com-
Open ports:
80,443,3478/udp,5349/tcp,5349/udp -
Verify: visit
https://<your-domain>,/api/healthreturns 200; openchrome://webrtc-internalsand check forrelaycandidates (TURN)
In full mode, certificates are auto-issued and auto-renewed:
- Initial issuance: webroot (no downtime); system certs live under
/etc/letsencrypt/live/<domain>/; copied todocker/ssl/and 443 is enabled. - Initial issuance is followed by
docker compose up -d --force-recreate nginx(andcoturnwhen enabled) so the freshly generated HTTPS/SNI config is guaranteed to be mounted and active. Expect a brief reconnect window during this first cutover. - Renewal:
certbot.timeror/etc/cron.d/certbotruns daily; the deploy-hook copies new certs todocker/ssl/, sendsHUPto coturn when possible, and hot-reloads Nginx/Coturn (falling back to container restart if needed). - Lineage suffixes (-0001/-0002) are handled automatically.
- Firewall Configuration:
# Ubuntu/Debian
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3478/udp # TURN server- Container Network Isolation:
- All services run in isolated networks
- Only necessary ports exposed
- Internal services communicate using container names
All service logs are centrally stored in the logs/ directory:
logs/
βββ nginx/ # Nginx access and error logs
βββ backend/ # Backend application logs
βββ frontend/ # Frontend application logs
βββ coturn/ # TURN server logs
# Pull latest code
git pull origin main
# Re-run the same deployment command you used initially (examples)
bash ./deploy.sh --mode lan-http
# or (public IP)
bash ./deploy.sh --mode public --with-turn --with-nginx
# or (full domain)
bash ./deploy.sh --mode full --domain your-domain.com --with-nginx --with-turn --le-email you@domain.com# Backup Redis data
docker compose exec redis redis-cli BGSAVE
# Backup SSL certificates
tar -czf ssl-backup.tar.gz docker/ssl/
# Backup configuration files
cp .env .env.backup# Clean unused images and containers
docker system prune -f
# Update base images
docker compose pull
docker compose up -dbash ./deploy.sh --help
### Additional Notes
- In Docker environments, Next.js Image optimization is disabled by default (`NEXT_IMAGE_UNOPTIMIZED=true`) to avoid container loopback fetch failures on `/_next/image`. To enable it, set the variable to `false` and rebuild.
- With `--with-nginx`, the frontend is built to use same-origin API (`/api`, `/socket.io/`). Use the gateway URLs printed by the script; direct ports `:3002/:3001` are not recommended in production.- GitHub Issues: Technical questions and bug reports