How I Set Up a Secure Production VPS for a Dockerized Laravel App
1/31/2025
[!tip] π‘ This article documents how I deploy Laravel applications securely on a VPS using Docker, Cloudflare Tunnel, and SSH hardening β following real-world production practices.
Tech stack: Laravel Β· Docker Β· Nginx Β· MariaDB Β· Ubuntu Server Β· Cloudflare Tunnel
Category: DevOps Β· Backend Β· VPS Security
Level: Intermediate β Advanced
Why I Built This Laravel VPS Setup
Deploying a Laravel application to a VPS is easy. Deploying it securely and correctly is where most setups fail.
I built this VPS configuration to follow real-world production practices, focusing on:
- Minimal attack surface
- Docker-first architecture
- Secure database access
- Clean, observable logging
- No exposed infrastructure ports
This is the same approach I would use for a serious personal project or a small production system.
Core Design Principles
- β No public MySQL / MariaDB ports
- β No direct Nginx exposure
- β No password-based SSH
- β SSH key-only authentication
- β Dockerized Laravel services
- β Cloudflare Tunnel for HTTPS access
- β Logs written to stdout/stderr (cloud-native)
VPS Base Configuration
Operating system: Ubuntu Server 22.04+
User model: Non-root deploy user with sudo
Firewall: UFW with minimal rules
Create deploy user
adduser deploy
usermod -aG sudo deploy
Secure SSH configuration
PermitRootLogin no
PasswordAuthentication no
This significantly reduces brute-force and privilege escalation risks.
Using Docker for Laravel Deployment on a VPS
Docker is the foundation of the system. All services run inside containers:
- Laravel (PHP-FPM)
- Nginx
- MariaDB
- Optional observability tools
curl -fsSL <https://get.docker.com> | sudo sh
sudo usermod -aG docker deploy
Why Docker?
- Consistent environments
- Easy rebuilds
- No dependency pollution on the host
- Portable across VPS providers
Project Structure
/var/www/app
βββ docker-compose.yml
βββ Dockerfile
βββ .env
βββ app/
βββ public/
βββ storage/
This layout keeps everything contained, auditable, and easy to back up or migrate.
Dockerized Laravel (PHP-FPM)
Key decisions:
- PHP 8.2
- Minimal required extensions
- Non-root container user
- Logs sent to stderr
LOG_CHANNEL=stderr
LOG_LEVEL=debug
This ensures Laravel logs are Docker-friendly and production-correct.
Nginx Configuration (Private by Default)
Nginx is bound to localhost only:
ports:
- "127.0.0.1:80:80"
Benefits:
- No public web server ports
- Reduced attack surface
- Access only via Cloudflare Tunnel
Secure MariaDB Setup
The database runs inside Docker and is bound to localhost only:
ports:
- "127.0.0.1:3306:3306"
Security benefits:
- Database accessible only from the VPS
- Safe for SSH tunneling
- Zero public exposure
Secure Database Access with DBeaver
Administrative database access is handled through an SSH tunnel:
ssh -N -L 3307:127.0.0.1:3306 deploy@VPS_IP
DBeaver connects to:
- Host:
127.0.0.1 - Port:
3307
This allows full GUI access without exposing the database.
Cloudflare Tunnel for Public Access
Instead of opening ports 80 or 443, the application is exposed using Cloudflare Tunnel.
ingress:
- hostname: app.example.com
service: <http://localhost:80>
Benefits
- No public IP exposure
- Automatic HTTPS
- Built-in DDoS protection
- Zero-trust friendly
Laravel Logging Strategy
Default (Recommended)
- Laravel logs β stderr
- Docker captures logs
- Inspect with:
docker logs laravel-app
Optional Upgrade
- Grafana + Loki
- Centralized log search
- Error dashboards
- Alerting
Centralized logging is optional and can be added later if needed.
Grafana Security (Optional)
If observability tools are enabled:
- User registration disabled
- Anonymous access disabled
- Routed only through Cloudflare Tunnel
This keeps internal tools private.
Final Security Checklist
- β No public database ports
- β No public web ports
- β SSH key-only access
- β Root login disabled
- β Docker-native logging
- β Cloudflare Tunnel enabled
Final Thoughts
This Laravel VPS setup prioritizes security, simplicity, and realism.
It avoids unnecessary complexity while still following:
- Modern DevOps practices
- Docker-native deployment patterns
- Zero-trust networking principles
Itβs a setup I would confidently run in production and reuse for future projects.
[!tip] π‘ πΌ What this project demonstrates: β’ Secure VPS configuration β’ Docker-based Laravel deployment β’ Cloudflare Tunnel usage β’ Production-grade logging practices β’ Real-world DevOps decision making