With the world dealing with COVID-19 by social distancing many gaming groups are left looking for ways to game while keeping their distance. This post will provide an overview for installing Foundry Virtual Tabletop, a software specifically designed to support playing table top games remotely with a group. I found this overview helpful in comparing Virtual Table Top options. One unique feature of Foundry is it uses a self hosted one time purchase model. In this post I’ll be using the same tools I’ve used in previous posts to deploy local or to the cloud provider of your choice.


Since this post is targeted at people interested in Foundry I won’t make the assumption you are familiar with Juju, Charms, or LXD. If you are you can move onto the installation steps below. For this setup I’m going to start from a clean Ubuntu Bionic (18.04) machine, if you want to follow along.

While these tools have a lot of additional configuration options I will only go over what’s necessary to get started. You can dig into more advanced networks and configuration options when/if needed at a later time.

The tools

For this guide we’ll install the software into LXD containers locally. This will let us test out Foundry and the automation tools and remove the containers when we’re done. The deployment will utilize Juju to manage the software and can be used to deploy on different environments. While we’re going to deploy locally on LXD the same tools and process can be used to deploy to a variety of cloud providers.

Setup the local environment

We’ll need LXD and Juju both of which can be installed as snaps.

# Instal snaps
sudo snap install lxd
sudo snap install juju --classic
# Setup lxd with defaults
lxd init --auto
# Verify LXD
lxc launch ubuntu:18.04
lxc list

This should install the dependencies and launch a test container. The final command lxc list should provide a table showing your new container.

|      NAME      |  STATE  |        IPV4            | IPV6 |    TYPE    | SNAPSHOTS |
| capital-martin | RUNNING | (eth0)  |      | PERSISTENT | 0         |

We don’t need that container it was just a test, remove it with lxc delete --force capital-martin, use the name that was generated for your container.

With LXD setup we’ll establish a local Juju environment, the processes of setting up a provider is known as bootstrapping.

juju bootstrap localhost lxd-controller

You can now check check the status of your juju environment with:

$ juju status
Model    Controller  Cloud/Region         Version  SLA          Timestamp
default  local-lxd   localhost/localhost  2.7.5    unsupported  18:38:05-05:00

Model "admin/default" is empty.

You’re all set, if you have any issues more detailed instructions are available on the juju lxd documentation.


Let’s start by deploying a bundle which will get the install running and then review the details of what it is doing. A bundle is simply a yaml file that completely defines a set of applications and options in a single file. Create a file named bundle.yaml with the following contents in it.

series: bionic
        charm: cs:~pirate-charmers/foundryvtt
        num_units: 1
            proxy_port: 80
            foundryvtt: ./foundryvtt-0.5.5.zip
        charm: cs:~pirate-charmers/haproxy
        num_units: 1
- - haproxy:reverseproxy
  - foundry:reverseproxy

Foundry currently distributes the software via foundryvtt.com and requires a one time purchase. At the time of this writing it can be purchased as pre-release, with the full launch planned for 2020-05-22. You will need the Zip file for Linux be sure the file is in the same directory as the bundle.yaml and that the name of the file matches the filename shown above. If you have a newer version simply update the name for the .zip file in the bundle.yaml to match your file.

With the bundle.yaml and foundry.zip you can start the deploy:

juju deploy ./bundle.yaml

You can see the status of the install with juju status --color. I prefer to watch it for a continual update watch -c juju status --color --relations.

When the deploy is finished you’ll see a status that looks like:

Model    Controller  Cloud/Region         Version  SLA          Timestamp
default  local-lxd   localhost/localhost  2.7.5    unsupported  19:36:38-05:00

App      Version  Status  Scale  Charm       Store       Rev  OS      Notes
foundry           active      1  foundryvtt  jujucharms    4  ubuntu
haproxy  0.1.8    active      1  haproxy     jujucharms    8  ubuntu

Unit        Workload  Agent  Machine  Public address  Ports   Message
foundry/0*  active    idle   0           Unit is ready
haproxy/0*  active    idle   1   80/tcp

Machine  State    DNS            Inst id        Series  AZ  Message
0        started  juju-53a674-0  bionic      Running
1        started  juju-53a674-1  bionic      Running

Relation provider     Requirer              Interface     Type     Message
haproxy:reverseproxy  foundry:reverseproxy  reverseproxy  regular

What was deployed

This bundle deploys two pieces of software.

  • FoundryVTT - The virtual table top server
  • Haproxy - Provides reverseproxy for FoundryVTT

This closely mirrors the configuration as described for setting up an Ubuntu VM on the community Wiki. Where the instructions on the Wiki use NGINX I’m using HAProxy both serve as a reverse proxy for Foundry.

Each software that Juju deploys is controlled via a piece of software known as a Charm. The HAProxy and FoundryVTT charms are publicly available and manage the installation and operation of each of those pieces of software. In this case they handle the setup much like the Wiki. Foundry installs Node, the provided Zip file, sets up a daemon to keep it running, and registers with HAProxy.

HAProxy has been installed and sets up the reverseproxy that is requested by Foundry. This is done via a relation which is shown at the bottom of the bundle. The relation is a known connection point between charms, in this case it’s named reverseproxy and we’ve connected Foundry to HAProxy as they both support the relation type.

In the juju status output you’ll see a table with a section for App, Unit, Machine, and Relation. Apps are the charms that are in the environment and tells you where it was installed from (jujucharms) and the Revision. Units are an actual instance of the application. In this case ‘foundry/0’ and ‘haproxy/0’. The Units tell you the IP address they currently have, what machine they are on, and some status about the unit. Machines are a list of all machines in an environment where machines can be bare metal, containers, virtual machines, etc. Machines aren’t terribly interesting here, but on a cloud provider you might install multiple units on a single virtual machine. Finally, a table of relations tells you which relations currently exist, there is one as discussed above between haproxy and foundry.

Accessing Foundry

With the deployment settled and all units showing active/idle we can access foundry. From the status above we’ll use the IP Address for HAProxy and the subdomain ‘foundry’ which is the default set on the foundry charm. Open a browser to http://foundry. and you should be greeted with the Activation Screen.

Foundry Registration

The use of xip.io is a convenience domain that lets us use an IP address like a domain name.

You can log into HAProxy or Foundry if you like with the juju ssh command. Also of interest is juju scp which lets you copy files to/from machines. For example you might want to copy a file to foundry to use as the background image for a world. You can do that with juju scp ./my-awesome-background.png foundry/0 or access foundry to see logs juju ssh foundry/0. If you do put files on Foundry you’ll find the data path the charm uses is /opt/foundry/userdata.

As I continue to update the Foundry charm additional actions for operating the software may become available. Charms can be upgraded with juju upgrade-charm command allowing you to pull in any fixes or changes. If you find bugs or features that should be part of the charm you can file them on the github page for the charm. Additionally, I idle on the Foundry Discord server with the user name Zarek if you want to ping me on the #installation-support channel.

Cleaning up

When you’re done and want to remove the containers you can remove the model with all the applications and machines and reset to a new clean default model with the command juju destroy-model -y default && juju add-model default

From here you can redeploy with the same or new configurations if you like.

Production use

With Juju you can target several cloud providers to deploy your software onto. With the same charms shown here you can deploy to whichever provider you prefer. For self hosing in a lab environment the LXD or MAAS providers allow you to deploy to either containers or bare metal machines of your choosing. I’ve run labs hosted on a single machine as LXD containers as well as a multi-machine lab deployed with MAAS.

By default if the option proxy_port is not set it will use 443 to provide a secure connection. The details of setting up HAProxy with Letsencrypt are beyond the scope of this post but it is supported by the charm and an example of setting it up can be seen in the previous blog post about bundle weechat. In that post, HAProxy was also used, and the section Accessing weechat via relay walks through the use of ddclient and HAProxy to setup a domain and Letsencrypt. As mentioned before feel free to ask questions via Discord if you are ready to plan a roll out to a cloud provider or hardware in your lab. Additionally, the weechat post demonstrates installing multiple charms on a single host with HAProxy being deployed directly to the machine and other software in a LXD Container on the machine. This same setup will let you run FoundryVTT and HAProxy on a single cloud machine.

In my experience if you deploy to a cloud provider the instance size needed to run the software is not very high. However, I’ve seen instances that die during installation due to lack of resource on small instances trying to install packages. One way to deal with that is to install with a default machine size and after it’s up and running shut the instance down and resize to a smaller instance.