This tutorial is targeted for fresh Ubuntu (maybe Debian, too, but I won’t promise 😛)

So, you booked a fresh and clean VPS server instance, now ssh into it and start shelling around:

$ sudo apt update -y && sudo apt upgrade -y --no-install-recommends

$ sudo apt install -y fail2ban ufw

$ service apache2 stop - just in case Apache blocks port 80! Having ports 80 and 443 preoccupied will give you a hard time, as a beginner.

Lock your VPS for root and password login

PLEASE exchange your-username in the following lines with your proper chosen username

  • generate your custom (main/admin/owner) user account
    $ adduser your-username

(adduser vs useradd)

  • give this user sudo permissions
    $ usermod -aG sudo your-username

  • switch roles to your-username
    $ su - your-username

  • prepare to ssh only login restrictions
    $ mkdir ~/.ssh && chmod 700 ~/.ssh $ touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys

  • copy the content of your .pub ssh key into the authorized_keys
    $ nano ~/.ssh/authorized_keys

  • restrict ssh settings for session $ sudo nano /etc/ssh/sshd_config and change the permission to “NO” for the following

    # /etc/ssh/sshd_config
    PasswordAuthentication no
    PermitRootLogin no
    

    I absolutely recommend reading a little deeper and restrict what you DO NOT need!

  • recheck your authorized_keys are set properly and sshd_config is fine, then:
    $ sudo systemctl restart ssh.service

  • in a second terminal you should now be able to login as:
    $ ssh -A your-username@your-vps-server.ip.com

  • another fresh terminal window and verfiy you cannot login as root anymore
    $ ssh root@your-vps-server.ip.com

Install Docker

$ curl -fsSL https://get.docker.com -o get-docker.sh

$ sh get-docker.sh

$ rm get-docker.sh

*Read carefully what docker prints in the end AND read the documentation

Setup a basic Firewall for security

There is a neat blog post with further apps, that help you keep the guards up on Digital Ocean!

$ ufw default deny incoming

$ ufw default allow outgoing

$ ufw allow 22/tcp this usually is ssh (make sure you allow your chosen ssh key)

$ ufw allow 80/tcp for http requests

$ ufw allow 443/tcp for https requests

$ ufw show added check your settings before enabling

$ ufw enable

Read more about UncomplicatedFirewall here

Keep in mind, you might need other ports now or in the future 🪲

What’s Fail2Ban?

Protect, watch, track, lock suspicious logins using Fail2Ban

Read a bit and setup, as much as you need.

Taken from the Traefik Documentation (2019-03-31)

From this point on, you should make yourself a little familiar about user rights and folder permissions. This is more an inspiration on how you can achieve this yourself.

I advise you read it yourself ;-) Traefik - Let’s Encrypt & Docker

$ docker network create web

$ mkdir -p /opt/traefik

$ touch /opt/traefik/docker-compose.yml

# /opt/traefik/docker-compose.yml
version: "3.9"

networks:
  web:
    external: true

services:
  traefik:
    image: "traefik:2.5.3"
    container_name: "traefik"
    restart: always
    networks:
      - web
      - default
    command:
      - "--providers.docker.swarmMode=false"
      # - "--providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`)"
      - "--log.level=DEBUG"
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=web"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge=true"
      - "--certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web"
      # - "--certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.myhttpchallenge.acme.email=INSERT-YOUR-EMAIL-HERE@test.test"
      - "--certificatesresolvers.myhttpchallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      # - "8080:8080"
    volumes:
      - "traefik-public-certificates:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

volumes:
  # Create a volume to store the certificates, there is a constraint to make sure
  # Traefik is always deployed to the same Docker node with the same volume containing
  # the HTTPS certificates
  traefik-public-certificates:

Example Docker Compose

version: "2.1"

services:
  app:
    image: my-docker-registry.com/my-awesome-app/app:latest
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: always
    networks:
      - web
      - default
    expose:
      - "9000"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=web"
      - "traefik.http.routers.my_nice_app.entrypoints=web"
      - "traefik.http.routers.my_nice_app.rule=Host(`app.test.test`)"
      - "traefik.http.routers.my_nice_app.middlewares=https_redirect"
      - "traefik.http.middlewares.https_redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.my_nice_app_https.entrypoints=websecure"
      - "traefik.http.routers.my_nice_app_https.tls.certresolver=myhttpchallenge"
      - "traefik.http.routers.my_nice_app_https.rule=Host(`app.test.test`)"
      - "traefik.http.services.my_nice_app.loadbalancer.server.port=9000"

  db:
    image: my-docker-registry.com/back-end/5.7
    restart: always

  redis:
    image: my-docker-registry.com/back-end/redis:4-alpine
    restart: always

  events:
    image: my-docker-registry.com/my-awesome-app/events:latest
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: always
    networks:
      - web
      - default
    expose:
      - "3000"
    labels:
      - "traefik.backend=my-awesome-app-events"
      - "traefik.docker.network=web"
      - "traefik.frontend.rule=Host:events.my-awesome-app.org"
      - "traefik.enable=true"
      - "traefik.port=3000"

networks:
  web:
    external: true

Most Important Tip in the Docs

Always specify the correct port where the container expects HTTP traffic using traefik.port label. If a container exposes multiple ports, Traefik may forward traffic to the wrong port. Even if a container only exposes one port, you should always write configuration defensively and explicitly.