Using Docker to consolidate services

Using docker, you can host multiple applications or services on one VPS.

In my case, I had the Unifi controller, the UISP service, and the open-sourced version of Bitwarden (Vaultwarden) installed each on their own VPS.

There are multiple different VPS providers. Here are 3 that I have personally used and still use and have had no issues with.

  • Vultr – These guys have a multitude of options with your virtual servers and charge by the hour. They have VPS’s as low as $5.00 per month.
  • Linode – These guys are very similar to Vultr. But they do offer a community scripts option that allows other users to share easy to setup apps.
  • Interserver – Interserver offers shared hosting as well as servers. They do offer the cheapest VPS with 2GB of ram though.

Server Setup

I started with an Ubuntu VPS. But, most service providers will offer a VPS with Docker pre-installed. In case you are using a vanilla server, here is how to setup docker.

Login to the VPS using Putty or your favorite SSH client. Run the following commands to install docker.

sudo apt-get install ca-certificates curl gnupg lsb-release
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-compose

Next setup a user called docker and optionally change the shell for easier use.

adduser docker --ingroup docker
sudo chsh docker -s /bin/bash

Now, let’s login as docker

su docker

Create Proxy Network

Let’s create a proxy network that will be used for all of the containers to communicate with the outside world.

docker network create proxy

Folder Structure

Next, you will want to create your folder structure. In our usage case, we are installing 5 different docker containers.

  1. Traefik – This is a proxy that allows us to use SSL to access apps.
  2. Watchtower – This is optional. It will auto update your containers.
  3. Unifi Controller – For managing customer sites.
  4. UISP – For managing customer Ubiquiti devices.
  5. Vaultwarden – Password management system.

These are the images I chose, but you can modify if you prefer a different one.

Containers

Once the folders are setup, we will need to add a file called docker-compose.yml in each container setup folder. For instance, for Traefik, we will be putting this file in /opt/containers/traefik.

Next, we will create a data or config folder in each container setup folder. This folder is to hold the persistent data for each container. This is necessary as docker containers don’t save any data in the container itself. In the docker-compose.yml files, we will be specifying volume mappings which will keep the data safe.

Traefik

For Traefik, we will need to add a couple of files into the /opt/containers/traefik/data folder.

First, we need to create the files needed for Traefik. The traefik.yaml stores basic setup info to get Traefik running. The acme.json file will start out as an empty file and is used by LetEncrypt to store the certificates for each “site”.

touch /opt/containers/traefik/data/acme.json
chmod 600 /opt/containers/traefik/data/acme.json
touch /opt/containers/traefik/data/traefik.yml

Next, let’s populate the traefik.yaml file with the basics.

api:
  dashboard: true

entryPoints:
  http:
    address: ":80"
  https:
    address: ":443"

serversTransport:
  insecureSkipVerify: true

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false

certificatesResolvers:
  http:
    acme:
      email: [email protected]
      storage: acme.json
      tlsChallenge: {}

Before we add the docker-compose.yml file, we need to create a username and password. Traefik will use this to password protect your webui. Run the following command in the shell, while changing the USERNAME and PASSWORD fields to what you want them to be.

echo $(htpasswd -nb USERNAME PASSWORD) | sed -e s/\\$/\\$\\$/g

You will get a response similar to below.

USERNAME:$$apr1$$ogW8Cyb5$$IRWEZqOJ2IbjB7GmEdHss/

To create the docker-compose.yml file, let’s go back to /opt/containers/traefik folder. Use the nano command and populate with the following. Make sure to replace traefik.domain.com with your host and domain (both instances) and replace USERNAME:PASSWORD with the string above.

version: '3'
networks:
  proxy:
    external: true

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/acme.json:/acme.json
    labels:
      - "traefik.enable=true"
      - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
      - "traefik.http.middlewares.redirect-https.redirectScheme.permanent=true"
      - "traefik.http.routers.traefik.entrypoints=http"
      - "traefik.http.routers.traefik.rule=Host(`traefik.domain.com`)"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=USERNAME:PASSWORD"
      - "traefik.http.routers.traefik.middlewares=redirect-https"
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.domain.com`)"
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
      - "traefik.http.routers.traefik-secure.tls=true"
      - "traefik.http.routers.traefik-secure.tls.certresolver=http"
      - "traefik.http.routers.traefik-secure.service=api@internal"

Now, to get this running, you simply to need run a single command.

docker-compose up -d

You should now be able to access your Traefik webui at https://traefik.domain.com after entering your username and password.

Tip: To make things easier when accessing webui, save the username and password in your favorites with the URL, ie https://username:[email protected]

Traefik is the only container we will need to add any files other than the docker-compose.yml file.

For the remaining containers, simply enter into the folder of the container, ie /opt/containers/appname and populate the file with the info below. After creating the docker-compose.yml file and adding the necessary info, you will run the same docker-compose command as above. This file needs to be run from the same folder containing the docker-compose.yml file.

Watchtower

version: "3"
services:
  watchtower:
    image: containrrr/watchtower
    container_name: watchtower
    restart: always
    environment:
      - "WATCHTOWER_CLEANUP=true"
      - "WATCHTOWER_SCHEDULE=0 2 * * * *"
      - "WATCHTOWER_NOTIFICATIONS=msteams"
      - "WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL=https://ms.teams.webhook"
      - "WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA=true"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /etc/localtime:/etc/localtime:ro

I am using MS Teams for notifications. You can either remove the 3 notification lines, change the URL, or change the type of notification using the help section for Watchtower. Click here to get more information about notifications.

Unifi Controller

version: '3'
networks:
  proxy:
    external: true

#SERVICES
services:
  unifi:
    image: jacobalberty/unifi:latest
    container_name: unifi
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 1900:1900/udp
      - 3478:3478/udp
      - 5514:5514
      - 6789:6789
      - 8080:8080
      - 8443:8443
      - 8843:8843
      - 8880:8880
      - 10001:10001/udp
    networks:
      - proxy
    environment:
      - "PGID=1000"
      - "PUID=1000"
      - "TZ=America/Chicago"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      # Redirect to get the certs
      - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
      - "traefik.http.middlewares.redirect-https.redirectScheme.permanent=true"
      # routers & services
      - "traefik.http.routers.unifi-https.rule=Host(`unifi.domain.com`)"
      - "traefik.http.routers.unifi-https.entrypoints=https"
      - "traefik.http.routers.unifi-https.tls=true"
      - "traefik.http.routers.unifi-https.tls.certresolver=http"
      - "traefik.http.routers.unifi-https.service=unifi"
      - "traefik.http.routers.unifi-http.rule=Host(`unifi.domain.com`)"
      - "traefik.http.routers.unifi-http.entrypoints=http"
      - "traefik.http.routers.unifi-http.middlewares=redirect-https"
      - "traefik.http.routers.unifi-http.service=unifi"
      - "traefik.http.services.unifi.loadbalancer.server.scheme=https"
      - "traefik.http.services.unifi.loadbalancer.server.port=8443"

    volumes:
      - ./config/:/unifi

UISP

Note: The UISP container requires a minimum of 2GB memory to run.

version: '3'
networks:
  proxy:
    external: true

#SERVICES
services:
  uisp:
    image: nico640/docker-unms:latest
    container_name: uisp
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 2055:2055
    networks:
      - proxy
    environment:
      - "PGID=1000"
      - "PUID=1000"
      - "TZ=America/Chicago"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      # Redirect to get the certs
      - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
      - "traefik.http.middlewares.redirect-https.redirectScheme.permanent=true"
      # routers & services
      - "traefik.http.routers.uisp-https.rule=Host(`uisp.domain.com`)"
      - "traefik.http.routers.uisp-https.entrypoints=https"
      - "traefik.http.routers.uisp-https.tls=true"
      - "traefik.http.routers.uisp-https.tls.certresolver=http"
      - "traefik.http.routers.uisp-https.service=uisp"
      - "traefik.http.routers.uisp-http.rule=Host(`uisp.domain.com`)"
      - "traefik.http.routers.uisp-http.entrypoints=http"
      - "traefik.http.routers.uisp-http.middlewares=redirect-https"
      - "traefik.http.routers.uisp-http.service=uisp"
      - "traefik.http.services.uisp.loadbalancer.server.scheme=https"
      - "traefik.http.services.uisp.loadbalancer.server.port=443"

    volumes:
      - ./data/:/config

Vaultwarden

version: '3'
networks:
  proxy:
    external: true

#SERVICES
services:
  bitwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    environment:
      - "SIGNUPS_ALLOWED=false"
      - "ADMIN_TOKEN=CHANGE_TO_YOUR_TOKEN"
      - "DOMAIN=vault.domain.com"
      - "TZ=America/Chicago"
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      # Redirect to get the certs
      - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
      - "traefik.http.middlewares.redirect-https.redirectScheme.permanent=true"
      # routers & services
      - "traefik.http.routers.bitwarden-ui-https.rule=Host(`vault.domain.com`)"
      - "traefik.http.routers.bitwarden-ui-https.entrypoints=https"
      - "traefik.http.routers.bitwarden-ui-https.tls=true"
      - "traefik.http.routers.bitwarden-ui-https.service=bitwarden-ui"
      - "traefik.http.routers.bitwarden-ui-http.rule=Host(`vault.domain.com`)"
      - "traefik.http.routers.bitwarden-ui-http.entrypoints=http"
      - "traefik.http.routers.bitwarden-ui-http.middlewares=redirect-https"
      - "traefik.http.routers.bitwarden-ui-http.service=bitwarden-ui"
      - "traefik.http.services.bitwarden-ui.loadbalancer.server.port=80"
      - "traefik.http.routers.bitwarden-websocket-https.rule=Host(`vault.domain.com`) && Path(`/notifications/hub`)"
      - "traefik.http.routers.bitwarden-websocket-https.entrypoints=https"
      - "traefik.http.routers.bitwarden-websocket-https.tls=true"
      - "traefik.http.routers.bitwarden-websocket-https.tls.certresolver=http"
      - "traefik.http.routers.bitwarden-websocket-https.service=bitwarden-websocket"
      - "traefik.http.routers.bitwarden-websocket-http.rule=Host(`vault.domain.com`) && Path(`/notifications/hub`)"
      - "traefik.http.routers.bitwarden-websocket-http.entrypoints=http"
      - "traefik.http.routers.bitwarden-websocket-http.middlewares=redirect-https"
      - "traefik.http.routers.bitwarden-websocket-http.service=bitwarden-websocket"
      - "traefik.http.services.bitwarden-websocket.loadbalancer.server.port=3012"

    volumes:
      - ./data/:/data

I hope this helps someone who is struggling as I was.

Posted in Information Technology.