The 10-Minute Webserver Setup
Setting up a web server used to mean wrestling with Apache configs, manual SSL certificates, and DNS headaches. Add subdomains? That's another afternoon gone.
Not anymore. Jason Wilder's nginx-proxy changed the game: containers are auto-discovered, reverse-proxied, and - combined with the ACME companion - get free Let's Encrypt certificates without touching a config file. Add a subdomain? Three lines. SSL? Automatic. Renewal? Handled.
Here's the setup I use to host multiple sites on a single VPS (Virtual Private Server) - basically a Linux machine in the cloud you rent for a few euros a month.
But fair warning: even with modern tools, you should know what you're doing. This isn't click-and-deploy WordPress hosting. You'll need basic Linux and Docker skills - or the willingness to learn them.
The Stack
| Component | Purpose |
|---|---|
| Docker | Container runtime |
| nginxproxy/nginx-proxy | JWilder's reverse proxy, auto-discovers containers |
| nginxproxy/acme-companion | Let's Encrypt integration, auto-issues & renews SSL |
| tecnativa/docker-socket-proxy | Security layer, limits Docker API access |
Don't worry about downloading these manually - Docker Compose pulls everything automatically on first run.
What You Get
graph TD
Internet((Internet)) --> nginx-proxy
nginx-proxy --> site1[Site A]
nginx-proxy --> site2[Site B]
nginx-proxy --> site3[Site C]
acme-companion -.->|SSL certs| nginx-proxy
Any container with the right environment variables gets reverse-proxied and SSL-certified automatically.
Prerequisites
- Ubuntu VPS with root SSH access
- A domain pointing to your server's IP
- 10 minutes
Step 1: Install Docker
bash
curl -fsSL https://get.docker.com | sh
systemctl enable --now dockerRunning as non-root? Add yourself to the docker group: sudo usermod -aG docker $USER and re-login.
Step 2: Create the Stack
mkdir -p ~/nginx-proxy && cd ~/nginx-proxy
docker network create nginx-proxy
Create docker-compose.yml (with your correct email):
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: docker-socket-proxy
restart: always
environment:
- CONTAINERS=1
- NETWORKS=1
- INFO=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-proxy
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./certs:/etc/nginx/certs:ro
- ./vhost.d:/etc/nginx/vhost.d
- ./html:/usr/share/nginx/html
environment:
- DOCKER_HOST=tcp://docker-socket-proxy:2375
depends_on:
- docker-socket-proxy
networks:
- nginx-proxy
- socket-proxy
acme-companion:
image: nginxproxy/acme-companion
container_name: acme-companion
restart: always
volumes:
- ./certs:/etc/nginx/certs:rw
- ./vhost.d:/etc/nginx/vhost.d
- ./html:/usr/share/nginx/html
- ./acme:/etc/acme.sh
environment:
- DEFAULT_EMAIL=your@email.com
- NGINX_PROXY_CONTAINER=nginx-proxy
- DOCKER_HOST=tcp://docker-socket-proxy:2375
depends_on:
- docker-socket-proxy
networks:
- nginx-proxy
- socket-proxy
networks:
nginx-proxy:
external: true
socket-proxy:
internal: true
Start it:
docker compose up -d
First run downloads all images (~200MB).
Step 3: Add Your First Site
Create a new folder and docker-compose.yml (with your correct host):
services:
mysite:
image: nginx:alpine
environment:
- VIRTUAL_HOST=mysite.example.com
- LETSENCRYPT_HOST=mysite.example.com
networks:
- nginx-proxy
networks:
nginx-proxy:
external: true
docker compose up -d
SSL certificate arrives within minutes. Done.
Full script on GitHub: github.com/appdoo/vps-setup
Resources
- Docker - Container runtime
- nginx-proxy - JWilder's reverse proxy
- acme-companion - Let's Encrypt automation
- docker-socket-proxy - Secure Docker API access
- Let's Encrypt - Free SSL certificates
Next up: This setup works and is reasonably secure. But what exactly is that socket proxy doing? Part 2 explains the Docker socket problem and why we're protecting against it.