Free, open-source video studio for podcasts and meetings, on a server you own. Guests join by link.
68
Record a podcast, or hold a meeting nobody else gets a copy of. Guests click a link and they are in - no account, no app, no time limit. You get a finished video of everyone, and a track per person if you want one. It runs on one server that you own.

fossstudio.org · Install · What you get · AGPL-3.0
One file, nothing to fill in. Save quickstart-compose.yml as docker-compose.yml on a Linux server with Docker, then:
docker compose up -d
Open your domain in a browser. The studio asks for the password you want, offers a passkey and two-factor, and asks where it lives. There is no setup code to find in a log: the first person to open a studio nobody owns yet claims it, and the setup screen is gone after that.
You need a domain pointed at the server and these ports open: 80 and 443 (TCP), then 3478 and the UDP ranges 40000-40003 and 49160-49189. The ranges are short on purpose - four ports hold a full room.
The longer install notes below cover bringing your own Nginx, a Cloudflare tunnel, Tailscale with no open ports at all, and what to do when everybody joins but nobody can hear anything.


Every browser used to prove the media path so far has been on a single machine. Two people in different houses is ordinary WebRTC through mediasoup with a relay behind it and should be fine - it just has not been watched working. Record a test session before a show that matters, and keep a second recording running for anything you cannot do again. That is ordinary studio practice rather than a warning about this one.
FOSSStudio is free software under the AGPL, and it always will be. Put the compose file on a machine you control and it is yours: no account, no key, no tier, no per-guest charge, no watermark, nothing phoning home. There is no analytics, no tracking, no crash reporting and no update check, and the code, the fonts and the relay are all served from your own machine. One thing leaves it, and only when you ask: turning on desktop notifications means the nudge travels through your browser maker's push service, as every web notification does. Everything in this repository is everything there is.
A studio needs a server with open UDP ports and enough bandwidth for everyone's video, which is more to think about than most web apps. If you would rather not, the people who write FOSSStudio also host it: Castmorphic runs this same software for you, with more built on top of it, and paying for that is what funds the work here. It is an alternative to self-hosting, not a better version of it. You will not find an advert for it inside the software, and you never will.
/host/. You set the password the first time you open the studio - at least twelve characters, and not one of the ones everybody guesses - and can add a passkey, so the private key stays on your own device and a stolen data folder yields nothing to log in with, and a second factor on top. There is no way to make a second account, because a second person who wants a studio runs their own copy - the license is there for exactly that. Guests need no account at all, which is the whole point of the link. The dashboard can have a domain of its own if you like: point host. at the server and it works, certificate included..wav or an .opus - the same audio the browser recorded, repackaged rather than re-encoded, in a form an editor opens without an extra library. A camera track is whatever the browser's own encoder made of the picture, one file per stretch, each told where in the take it starts, because silence can be manufactured without an encoder and a picture of nothing cannot.?output=1) with no join screen and no controls. It works as a browser source in OBS or anything like it, which is how a host who wants to broadcast the show does it, and it is just as useful for putting the session on a second screen. It is invisible to everyone in the session and can never appear in the recording. Copy it from the session row in the dashboard.FOSSStudio records people talking. It does not publish what it records, and it does not broadcast it.
Free software under the GNU AGPL v3.
NOTICE.md records who wrote what. FOSSStudio is Lightmorphic's own work throughout; the third-party material is mediasoup-client (ISC), RNNoise via @jitsi/rnnoise-wasm (Apache-2.0 over BSD-3-Clause) and the Manrope typeface (SIL OFL 1.1). Four runtime npm dependencies, listed there with their licenses.
See CHANGELOG.md for release history.
Node.js + mediasoup SFU, Caddy (HTTPS and certificates - commented out in the compose file, so bringing your own reverse proxy means leaving it that way), coturn (TURN relay), flat JSON files, no database. One Docker Compose stack, everything self-hosted. No page loads anything from another domain: the typeface, the scripts and the WASM all come from your own server.
server/: the app, signaling, media, auth, recording, opsserver/client/: sources for the two bundled browser assetsserver/test/: end-to-end tests (Playwright, fake camera/mic)web/: everything the browser loads (guest pages, host dashboard)scripts/: server setup, deploy, rollbackdocs/runbook.md: plain-language operations guidedata/: settings, recordings, backups (created at runtime; not in git)You need a Linux server with Docker, a public IP, a domain pointed at it, and ports 80/443 (TCP) plus 3478 and the media ranges 40000-40003 and 49160-49189 (UDP) open.
The stack runs on a normal bridge network and publishes those ports the ordinary way, host number equal to container number, so Dockge, Dokploy, Portainer and the rest list them and offer a link. The ranges are short because mediasoup carries every connection over the same few sockets: four ports hold a full room. (Running more than one studio on a host? RTC_MIN_PORT/RTC_MAX_PORT and TURN_MIN_PORT/TURN_MAX_PORT move the ranges, so instances never collide.)
The one-paste install. Save quickstart-compose.yml as docker-compose.yml anywhere on the server, and:
docker compose up -d
There is nothing to fill in. The file holds the image, the ports and the volume; the domain, the login and the secrets are settings inside the studio. It makes its own secrets on the first start and keeps them in its data volume, so nobody generates one with openssl and nobody pastes one anywhere.
Open your domain in a browser and the studio asks for the password you want, offers you a passkey and two-factor, and asks where it lives. There is no setup code: the first person to open a studio nobody owns yet claims it, and after that the setup screen is gone and the route behind it refuses everybody.
The minute that leaves open. Between the container starting and you opening the page, anybody who can reach that address could claim it instead. On a home network that is a minute with nobody looking, and it is how Jellyfin, Immich and Home Assistant all work. On a server with the port open to the internet it is a real window: keep the port shut until you have claimed it, or set REQUIRE_SETUP_CODE=1 in the app's environment, which puts back a code printed in docker compose logs app and asks for it before anyone may claim the studio. The studio takes the address from the connection and never from a header, so X-Forwarded-For: 127.0.0.1 buys a stranger nothing.
The password is enforced rather than advised: at least twelve characters, and not one of the ones everybody guesses. There are no rules about capitals and symbols. A passphrase is offered beside the box if you would rather not think of one.
Everything on that screen is in Settings afterwards, and the studio's own help is the Help tab at the foot of the dashboard's left-hand menu (/help on your install goes there too) - it works with no internet connection, has pictures of the real screens in it, and describes the version you have.
The full checkout (for hacking on it, or the deploy-from-a-dev-box flow): clone the repo, and then
bash scripts/server-setup.sh installs Docker, sets the firewall and creates the folder layout..env.example to .env if you want to change any of the defaults - ports, where the data folder lives, who may reach the web port. There are no secrets in it and the stack runs without one.docker compose up -d --build
Caddy fetches HTTPS certificates for your domain automatically. If other apps on the same host need to share ports 80/443, point CADDY_SITES_PATH at a folder of extra .caddy site files and the bundled Caddy serves those too (set CADDY_WWW_PATH as well for their static files - a site file then uses root * /srv/www/<name>). To deploy updates from a dev machine instead of building on the server: FOSSSTUDIO_HOST=root@<ip> scripts/deploy.sh (release folders with instant rollback via scripts/rollback.sh).
Already running Nginx or another proxy on your server and don't want a second one? Then leave the caddy service in docker-compose.yml commented out, which is how it ships, and start the stack as usual:
docker compose up -d --build
Set APP_BIND=127.0.0.1 in .env and the web port is published on loopback only - 127.0.0.1:3000 - so your proxy on this host can reach it and nothing else can. A proxy in another container on the same compose network reaches the app by service name (app:3000) instead. Terminating TLS is then yours to do: browsers refuse camera and microphone access without HTTPS.
Proxy on a different machine? Also fine - the proxy only ever carries the web half; guests' WebRTC media and the TURN relay go directly to this machine and never pass through a proxy. Three settings make it work:
APP_BIND - set it to this machine's private/VPN address so the proxy can reach the app, then firewall that port so only the proxy's IP can connect to it. (BIND_HOST is a different thing: the address the app listens on inside its container, which stays 0.0.0.0 or the published port reaches nothing.) The app must never be reachable from the open internet directly: cameras and cookies only work through the HTTPS front door.The Nginx block below is then identical, with proxy_pass pointing at this machine's address instead of 127.0.0.1:3000. Two things Nginx doesn't do automatically that Caddy does, both required or the app silently breaks:
/ws; without these headers the page loads but nothing ever connects.Secure-only, so the app requires HTTPS end-to-end. Terminate TLS in Nginx (e.g. with certbot) - plain HTTP cannot work.Example server block:
server {
listen 443 ssl http2;
server_name studio.example.com;
ssl_certificate /etc/letsencrypt/live/studio.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/studio.example.com/privkey.pem;
# The app sets its own security headers (CSP etc.); HSTS belongs
# here at the TLS terminator, matching the bundled Caddy setup.
add_header Strict-Transport-Security "max-age=31536000" always;
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-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name studio.example.com;
return 301 https://$host$request_uri;
}
Everything else - the media ports (40000-40003/udp and /tcp), coturn (3478 + 49160-49189/udp), the domain and public address in Settings - is identical to the Caddy path; only the HTTP(S) front door changes.
More set-up help (Cloudflare Tunnel, Tailscale, fixing sound and video) is on the install page.
Content type
Image
Digest
sha256:320362ff6…
Size
100.4 MB
Last updated
21 days ago
docker pull lightmorphic/fossstudio