Docker is a great way to run applications. It will keep it separate from the rest of your system and you’re running in the same environment regardless of the system hosting the container.

Traefik is a Docker-aware reverse proxy with a monitoring dashboard. Traefik also handles setting up your SSL certificates using Let’s Encrypt allowing you to securely serve everything over HTTPS.

Portainer is a web interface for managing Docker. This allows you to easily start/stop/restart your docker containers, manage their settings, or add more containers in the future.


To follow along with this tutorial, you will need the following:

  • A Debian based server - I recommend using Ubuntu 20.04.
  • A non-root user, currently logged in, with sudo access
  • Basic understanding of how to use the Linux command line
  • A domain with three A records, each pointing to your servers IP address. I recommend, and for the tutorial will be using the following: monitor, manage, play. You’re free to use any A records you wish, just modify the steps below as appropriate.



Throughout the tutorial, I will use nano as the text editor as I find it’s the most friendly to newer users. You’re welcome to use whatever editor you prefer.

Saving and Exiting with nano

  • CTRL+X
  • Press Y - to save modifications
  • Enter

Step 1 - Setting up your server

Note: You should be connected to your server via SSH or PuTTY - not using a web console from your hosting provider as pasting content doesn’t work well through web-based consoles.

This section will assume you’re running as a non-root user, with sudo permissions.

If you are running as root and do not have a regular user, run the follow commands to create one, replacing USERNAME with your desired username:

adduser USERNAME
usermod -aG sudo USERNAME

The last command will have switched you to your newly created user. You’re now ready to continue with the tutorial.

Installing the required packages:

Use the following command to install all of the required packages:

sudo apt update && sudo apt install apache2-utils docker-compose

Start docker and enable it to start on reboot

sudo systemctl enable --now docker

Setup your firewall - we will only allow traffic to the server over SSH, HTTP and HTTPS.

sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable

When you enable ufw there will be a warning that your SSH connection may get closed. It shouldn’t actually get closed as we’ve left port 22 open.

Last up, we need to give your user access to the docker group, with these last 2 commands.

sudo usermod -aG docker $USER
newgrp docker

Step 2 - Configuring and Running Traefik

Generating a secure password. In the command below, substitute secure_password with the actual password that you want to use.

htpasswd -nb admin secure_password

The output will look something like this:


If your string has any $ you will need to modify them to be $$ - this is because docker-compose uses $ to signify a variable. By adding $$ we still docker-compose that it’s actually a $ in the string and not a variable.

Take note of the output, we will need it near the end of this step.

Next up, we will start creating our config files. I recommend making a separate directory for each container. So, let’s make a folder and switch into it.

mkdir traefik && cd traefik

Now, let’s make our config file and start to edit it

nano traefik.yml

Copy and paste the following into your editor.

  dashboard: true

    address: ":80"
    address: ":443"

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

      storage: acme.json
        entryPoint: http

Modify the email address from to a valid email address. This is used for generating your SSL certificates.

Save and exit your editor (for nano check the Notes section above on how to Save & Exit)

On the topic of SSL, we’re going to need a file for our SSL information to be stored and persisted outside of the container. Let’s create that file now.

touch acme.json

Let’s lock down the permissions on this file so other users can’t read it.

chmod 600 acme.json

Up next is the docker-compose configuration.

nano docker-compose.yaml

Same as before, copy/paste the contents below into your editor and get ready to make a few modifications.

version: '3'

    image: traefik:v2.0
    container_name: traefik
    restart: unless-stopped
      - no-new-privileges:true
      - proxy
      - 80:80
      - 443:443
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
      - ./acme.json:/acme.json
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.entrypoints=http"
      - "traefik.http.routers.traefik.rule=Host(``)"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$apr1$ruca84Hq$mbjdMZBAG.KWn7vfN/SNK/"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(``)"
      - "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"

    external: true

Replace with your own domain on the following lines:

- "traefik.http.routers.traefik.rule=Host(``)"
- "traefik.http.routers.traefik-secure.rule=Host(``)"

Replace the auth information from the first command we ran, in this Step, on this line:

- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$apr1$ruca84Hq$mbjdMZBAG.KWn7vfN/SNK/"

Save and exit your editor.

We’re nearly there. Now we need to create the docker network with a quick command.

docker network create proxy

With that done, we’re ready to start our Traefik container.

Step 3 - Running the Traefik Container

Alright, we did all the heavy lifting for Traefik in Step 1. Time to start the container.

docker-compose up -d

This will build the docker container and start it up.

In your web browser, you should now be able to navigate to your Traefik monitoring page. You will be prompted to log in with the secure_password you used at the start of Step 1. In your browser, replace with your domain to login.

Step 4 - Configuring and Running Portainer

Assuming you’re still where we left off, let’s go back up 1 level of our directory path, create a new directory for portainer, and switch into it.

cd .. && mkdir portainer && cd portainer

Portainer will create a fair number of files and directories that it needs to persist between runs. Let’s make a directory for all that.

mkdir data

Now, we’re going to create our docker-compose config file.

nano docker-compose.yaml

Copy/paste the contents below. We have 2 lines to modify after.

version: '3'

    image: portainer/portainer:latest
    container_name: portainer
    restart: unless-stopped
      - no-new-privileges:true
      - proxy
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data:/data
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.entrypoints=http"
      - "traefik.http.routers.portainer.rule=Host(``)"
      - "traefik.http.middlewares.portainer-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.portainer.middlewares=portainer-https-redirect"
      - "traefik.http.routers.portainer-secure.entrypoints=https"
      - "traefik.http.routers.portainer-secure.rule=Host(``)"
      - "traefik.http.routers.portainer-secure.tls=true"
      - "traefik.http.routers.portainer-secure.tls.certresolver=http"
      - "traefik.http.routers.portainer-secure.service=portainer"
      - ""
      - ""

    external: true

Modify the two lines below, replacing with your domain.

- "traefik.http.routers.portainer.rule=Host(``)"
- "traefik.http.routers.portainer-secure.rule=Host(``)"

With that done, we’re ready to start the Portainer container.

Step 5 - Running the Portainer Container

Exactly the same as starting the Traefik container, we’re going to run:

docker-compose up -d

Now, if you browse to (replacing with your domain) you will be prompted to set up your login information.

On the screen asking you to Connect Portainer to the Docker environment you want to manage, select Local and press Connect.

We will use Portainer to set up and manage FoundryVTT

Step 6 - Configuring and Running FoundryVTT

Alright, we’re nearly there. For this next step, make sure you’ve downloaded the latest (linux) version of FoundryVTT zip file to your server. You don’t need to worry about unzipping it. I highly recommend storing it in its own directory, as the only file, and deleting it and replacing whenever a new version comes out.

If you have multiple FoundryVTT zip files in this directory, your docker deployment will break.

I will be storing mine in my users home directory in a folder called foundrydl. Please take note of where you store yours as we will need the folder name later.

We have 1 more console command to run before we get into using Portainer. We’re going to create a data directory for Foundry to store it’s data. Go ahead and run this command:

mkdir /home/$USER/foundrydata

Once you’ve logged into Portainer, you should see something that looks like the image below.

Go ahead and click on the only group on the page, called local. Here’s the next screen you’ll see.

Next, you’ll want to click on where it says 2 Containers. This next page is where you’ll see all of your running containers. Go ahead and click the Add Container button at the top of this page.

Go ahead and give your container a name. I will choose fvtt but you’re welcome to call it anything you want.

In the Image field, enter direckthit/fvtt-docker:latest.

The top part of your settings should look like the image below. Don’t hit Deploy the container just yet. We still have some more settings to add.

Scroll down to the Advanced container settings at the bottom of the page.

Under Command & logging enter the following into Entry Point: /opt/foundryvtt/

Next, click on the Volumes tab. Currently, you won’t see any volumes mapped. Go ahead and hit the map additional volume button.

In the container field, enter /data/foundryvtt. Right next to where you entered that, change the toggle from Volume to Bind. The next line should now say host.

The volume we’re currently adding is where Foundry will store all of your data, including your worlds, modules and uploaded images. At the start of this step we created this folder in our home directory. You’ll have to modify the path below to replace username with your users’ actual username.

In the host field enter /home/username/foundrydata. Don’t worry if the folder doesn’t exist - docker can handle that.

Next, we’re going to map our download directory (you’ll remember, above, that I’m using foundrydl as my folder). You’ll have to modify your path to match where you’ve saved your zip file. Let’s go ahead and hit that map additional volume button again.

For the newly created volume, enter the following in the container field: /host.

Just like before, hit the toggle to switch from Volume to Bind.

In the host filed, enter the path to where you’ve saved your zip file. For me, it will be /home/username/foundrydl.

When you’re done, your volumes should look something like this image.

Next up is adding the container to the right network. If you haven’t already guessed, you’re going to want to go ahead and hit the Network tab.

In the first field, Network, expand the dropdown and select proxy. You don’t need to change anything else on this tab. Here’s what your Network tab should look like:

Next, we’re going to jump ahead a little bit. Go over and click on the Restart policy tab. On this tab, change your Restart policy to Unless stopped. This will allow the container to automatically restart if it crashes.

Alright, one more tab to set up. I want to tell you that I saved the easiest for last. But, the reality is, I saved the longest one for last… Head on over the the Labels tab.

To add a label, you’re going to want to hit the add label button. Below you’ll find the list of all the labels you want to add. Be sure to change to your actual domain.

Name Value proxy
traefik.enable true
traefik.http.middlewares.foundryvtt-https-redirect.redirectscheme.scheme https
traefik.http.routers.foundryvtt-secure.entrypoints https
traefik.http.routers.foundryvtt-secure.rule Host(``)
traefik.http.routers.foundryvtt-secure.service foundryvtt
traefik.http.routers.foundryvtt-secure.tls true
traefik.http.routers.foundryvtt-secure.tls.certresolver http
traefik.http.routers.foundryvtt.entrypoints http
traefik.http.routers.foundryvtt.middlewares foundryvtt-https-redirect
traefik.http.routers.foundryvtt.rule Host(``) 30000

Once you’re done, it should look something like this:

Now you can go ahead and hit the Deploy the container button. Your container should now start up (it might take a minute) and you should be able to browse to (replacing with your domain) and find your freshly installed copy of Foundry VTT.

(Optional) Step 7 - Enabling Audio/Video Support

If you intend to use the built in audio/video support for Foundry and not another tool such as Discord, there’s one last step required.

First up, from the Portainer containers dashboard, select your Foundry container, and stop it.

Back in your SSH terminal, now that the container has been stopped, we’re going to edit one config file. Let’s use the following command to edit the config file in nano (as always, feel free to use your preferred editor):

sudo nano /home/$USER/foundrydata/Config/options.json

Look for the line that says proxySSL and change the value from false (or null, depending on your version) to true. Then you can save and exit nano.

Back in the Portainer containers dashboard, you can restart your Foundry container.

If you found this tutorial helpful and would like to show your appreciation, please consider becoming a Patron.