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.
Point your domain at the server. In your DNS control panel, an
Arecord forstudio.yourdomain.comwith the server's IP address. Wait a few minutes, then check from your own machine:host studio.yourdomain.comIt should print the server's address. If it does not, nothing below will work, so wait until it does.
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/udpMany 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.
Save the file from the front page as
docker-compose.yml, then switch HTTPS on: delete the#from thecaddy:block, from the two caddy volumes at the foot, and from the wholeconfigs: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.Start it:
docker compose up -dClaim it. Open
https://studio.yourdomain.comin 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.
Open the same ports as above except 80 and 443, which nginx already has.
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".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
certbotas you would for any other site, thensudo nginx -t && sudo systemctl reload nginx.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.
Install Tailscale on the machine and find its address:
tailscale ip -4It prints something like
100.94.12.7.Save the file and change nothing. Leave the Caddy block commented out - it cannot get a certificate for a
.ts.netname, so Tailscale issues one instead. No firewall changes at all.docker compose up -d, then hand it to Tailscale:tailscale serve --bg https / http://127.0.0.1:3000That prints the address it has given you, something like
https://homelab.your-tailnet.ts.net.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:
- Claim it straight away. Have the browser tab open before you
run
docker compose up -d, and the window is seconds long. - Keep the port shut until you have. Leave 443 closed in the
firewall, claim the studio over SSH with a port forward
(
ssh -L 3000:127.0.0.1:3000 you@server, thenhttp://127.0.0.1:3000), and open the port afterwards. - Put the code back. Set
REQUIRE_SETUP_CODE=1in the app's environment in the compose file. The studio then prints a code indocker compose logs appon every start where it has no owner, and asks for it before it will let anyone choose a password. Being able to read that log is the proof that the machine is yours. The code is held in memory only, changes on every restart, and is never written to disk.
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.
- 3000 - the studio itself, behind your HTTPS.
- 40000-40003, UDP and TCP - the media. Four ports carry a full room, because every connection shares them.
- 3478, and 49160-49189 UDP - the relay, for guests behind a firewall strict enough to stop their browser reaching you directly.
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.