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.
- Traefik – This is a proxy that allows us to use SSL to access apps.
- Watchtower – This is optional. It will auto update your containers.
- Unifi Controller – For managing customer sites.
- UISP – For managing customer Ubiquiti devices.
- 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.