The 10-Minute Webserver Setup

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

ComponentPurpose
DockerContainer runtime
nginxproxy/nginx-proxyJWilder's reverse proxy, auto-discovers containers
nginxproxy/acme-companionLet's Encrypt integration, auto-issues & renews SSL
tecnativa/docker-socket-proxySecurity 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 docker

Running 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


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.