FOSSStudio

Installing FOSSStudio

Everything the compose file used to explain, in one place, so the file itself can stay short.

The short version

Save the file from the front page as docker-compose.yml on a Linux server, then run docker compose up -d. Open your domain in a browser, choose a password, and the studio sets itself up. There is no code to find: the first person to open a studio nobody owns yet claims it, and after that nobody else can. That leaves a window between starting it and claiming it - see the minute before you claim it.

There is nothing to fill in beforehand. The domain, the login and the secrets are all settings inside the studio. It makes its own secrets on the first start and keeps them in its data volume, so you never type one and none of them sits in a file you might commit or paste somewhere.

Worked examples

Three whole setups, start to finish. Find the one nearest yours and follow it line by line. If none of them fits, the first is the one to read, because the others are that one with a change.

A. A VPS with a domain, nothing else on it

The ordinary case, and the easiest. You have rented a small Linux server, it has its own public address, and nothing else is using ports 80 and 443.

  1. Point your domain at the server. In your DNS control panel, an A record for studio.yourdomain.com with the server's IP address. Wait a few minutes, then check from your own machine:

    host studio.yourdomain.com

    It should print the server's address. If it does not, nothing below will work, so wait until it does.

  2. Open the ports in the server's firewall. On a machine using ufw:

    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw allow 3478
    sudo ufw allow 40000:40003/udp
    sudo ufw allow 40000:40003/tcp
    sudo ufw allow 49160:49189/udp

    Many hosts also have a firewall of their own in their control panel. Open the same ports there. This is the step people miss, and it is the one that makes a room open and stay silent.

  3. Save the file from the front page as docker-compose.yml, then switch HTTPS on: delete the from the caddy: block, from the two caddy volumes at the foot, and from the whole configs: block. Then change the app's port line from "3000:3000" to "127.0.0.1:3000:3000", because Caddy now reaches the studio from inside and nothing else needs to.

  4. Start it:

    docker compose up -d
  5. Claim it. Open https://studio.yourdomain.com in a browser, choose a password, add a passkey if you want one, and it is yours. Do this now rather than tomorrow: until somebody does, whoever reaches the address first could do it instead - see the minute before you claim it.

B. A server already running nginx

Something else on that machine already answers on 80 and 443, so Caddy cannot have them.

  1. Open the same ports as above except 80 and 443, which nginx already has.

  2. Save the file and change nothing. Leave the Caddy block commented out - that is how it ships - and leave the app's port line as "3000:3000".

  3. Add a server block to nginx. The two lines people forget are the WebSocket ones; without them the page loads perfectly and nothing ever connects:

    server {
        listen 443 ssl http2;
        server_name studio.yourdomain.com;
    
        ssl_certificate     /etc/letsencrypt/live/studio.yourdomain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/studio.yourdomain.com/privkey.pem;
    
        location / {
            proxy_pass http://127.0.0.1:3000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }

    Get the certificate with certbot as you would for any other site, then sudo nginx -t && sudo systemctl reload nginx.

  4. docker compose up -d, then open your domain and choose a password.

If nginx is on a different machine from the studio, everything above still applies, with two differences: point proxy_pass at the studio machine's address instead of 127.0.0.1, and keep the UDP ports open on the studio machine. The video never goes through nginx - it goes straight from each guest to the studio - so opening those ports on the proxy achieves nothing.

C. At home, on Tailscale, nothing exposed to the internet

No open ports, no certificate to manage, and no way for anyone outside your tailnet to join. For a weekly meeting, or a show with the same few people every week, this is the least trouble of the three.

  1. Install Tailscale on the machine and find its address:

    tailscale ip -4

    It prints something like 100.94.12.7.

  2. Save the file and change nothing. Leave the Caddy block commented out - it cannot get a certificate for a .ts.net name, so Tailscale issues one instead. No firewall changes at all.

  3. docker compose up -d, then hand it to Tailscale:

    tailscale serve --bg https / http://127.0.0.1:3000

    That prints the address it has given you, something like https://homelab.your-tailnet.ts.net.

  4. Open that address and choose a password. Nothing outside your tailnet can reach it, so there is no hurry about it.

Everyone you record with has to be on your tailnet, which means inviting them to it. That is the trade.

The minute before you claim it

There is no setup code. The first person to open a studio that nobody owns yet chooses the password, and from then on the setup screen is gone and the route behind it refuses everybody - on that machine as much as anywhere else. This is how Jellyfin, Immich and Home Assistant work, and it is what most people expect.

It has a cost, and it is worth saying plainly. Between the container starting and you opening the page, anybody who can reach that address could claim the studio instead of you. On a home network that is a minute with nobody looking. On a VPS with port 443 open to the internet, it is a real window, and a scanner finding a new address is not a fanciful thing.

So on a machine that is on the internet, do one of these:

With the code on, a browser opened on the machine the studio itself is running on is not asked for it - opening a connection to 127.0.0.1 is the same proof. The address is read from the connection, never from a header: X-Forwarded-For: 127.0.0.1 is a line of text anyone can put in a request, so a studio that believed it could be claimed by a stranger in one try. And because a reverse proxy on the same machine is the one thing whose own address can be loopback while the visitor is anywhere in the world, any sign of a proxy - any of the usual forwarding headers, which Caddy, nginx, Apache and Traefik all send - asks for the code anyway.

HTTPS is not optional

Browsers refuse camera and microphone access on a plain http page, and the login cookie is marked secure-only. So without a certificate there is no studio and nobody can even log in.

How you get one is your affair. The compose file has a commented-out Caddy block that fetches and renews a certificate on its own, which suits a public server with a domain and nothing else in front of it. To switch it on, delete the # from the caddy: block, from the two caddy volumes at the foot, and from the whole configs: block, then change "3000:3000" to "127.0.0.1:3000:3000" so the web port no longer needs to leave the machine.

Caddy asks the studio before fetching a certificate, which is how it works without being told your domain: before anybody owns the studio it will get one for whatever name is pointed at it, and once the studio has an owner, only for that owner's domain.

I already run nginx, Apache or another proxy

Then leave the Caddy block alone and point your own proxy at 127.0.0.1:3000. Two things Caddy does that nginx does not do by itself, both required: the WebSocket upgrade headers for /ws, without which the page loads and nothing ever connects; and terminating TLS. There is a working nginx block in the README.

If the proxy is on a different machine, the media still goes straight to the studio machine and never follows the proxy. So the UDP ports stay open on the studio machine, not on the proxy, and the studio needs a relay address that reaches it directly.

Tailscale

A studio reachable only inside your tailnet, with nothing exposed to the internet. Leave the Caddy block commented out - it cannot get a certificate for a .ts.net name - and let Tailscale issue one instead:

tailscale serve --bg https / http://127.0.0.1:3000

Guests have to be on your tailnet. That is the trade: no open ports, no certificate to manage, but nobody outside can join.

The ports, and why they match on both sides

Every port is published with the same number inside and outside the container. That is not tidiness. The media engine tells each guest which port to send their audio and video to, so a port that means something different outside the container is a room that opens and stays silent.

Why can nobody hear anything?

Almost always the media ports never reach the machine. Everyone joins, everyone appears in the list, and no sound or picture arrives. Check the UDP ports above are open, and that the public address in Settings is one guests can actually reach - behind a home router that is the router's address, with those ports forwarded to the machine.

How much disk?

More than people expect. At best quality each person's track is about 1.4 GB per hour, so four people for two hours is over 11 GB. There is a setting for smaller files at about 54 MB per person per hour, and the studio explains the trade where you choose it.

Updating

docker compose pull && docker compose up -d

Your settings and recordings live in the data volume and are not touched.