Installing Basement
Basement is one program: the server, the web app and the API in a single process. Download the installer for your computer (easiest), or use Docker or the standalone binary. Every download includes FFmpeg (Jellyfin's build, with hardware acceleration).
After installing, open http://<your-server>:8484, create the owner account, and add a library
folder under Admin → Libraries.
Installers
From getbasement.app/download or the latest release:
| Computer | File | What it does |
|---|---|---|
| Windows 10/11 | Basement-<version>-windows-x64-setup.exe |
Installs to Program Files, starts when you sign in (no window), adds Start menu items (Basement, Start, Stop, Uninstall) and a firewall rule for your home network. Data: %LOCALAPPDATA%\Basement. |
| macOS 12+ | Basement-<version>-macos-arm64.pkg (Apple Silicon) or -macos-x64.pkg (Intel) |
Installs Basement.app and starts it at login. Opening Basement.app opens it in your browser. Data: ~/Library/Application Support/Basement. Remove with sudo "/Applications/Basement.app/Contents/Resources/uninstall.sh". |
| Ubuntu, Debian | basement_<version>_amd64.deb (or _arm64) |
sudo apt install ./basement_<version>_amd64.deb. Runs as the basement system service (systemctl status basement). Data: /var/lib/basement; settings in /etc/default/basement. |
| Fedora, RHEL | basement-<version>-1.x86_64.rpm (or .aarch64) |
sudo dnf install ./basement-<version>-1.x86_64.rpm, then as for Debian. |
The installer opens Basement in your browser when it finishes (on Linux, go to
http://localhost:8484). Setting up from a different computer (a headless server, a NAS)
asks for a setup code, so nobody else on your network can claim the server first. Read it on the
server: sudo cat /var/lib/basement/setup-code (Linux packages), docker exec basement cat /data/setup-code (Docker), or the setup-code file in the data folder. Installing a newer version over an older one keeps your data and
restarts the server.
On Linux the service runs as the basement user, which needs read access to your media, for
example sudo setfacl -R -m u:basement:rX /srv/media (or add it to a group that can read it).
Releases are not code-signed yet, so the first time you open the installer:
- Windows says "Windows protected your PC": choose More info → Run anyway.
- macOS says it "cannot verify" the package: close the message, open System Settings → Privacy & Security, scroll down and click Open Anyway next to Basement, then open the package again. (On macOS 14 and earlier you can also right-click it and choose Open.)
Either way, you can check the download against SHA256SUMS on the release page first.
What you need
- Linux (x86-64 or ARM64), macOS (Apple Silicon or Intel) or Windows.
- 2 CPU cores and 2 GB RAM for direct play. Transcoding wants more: an Intel CPU with Quick Sync (anything from the last 8 years, including the N100), an NVIDIA GPU, or a fast CPU.
- Your media organized one movie per folder and one show per folder, for example
Movies/Alien (1979)/Alien (1979).mkvandTV/Goosebumps/Season 1/Goosebumps - S01E01.mkv. - Optional: a free TMDB API key for artwork, descriptions and ratings.
Docker (recommended)
# docker-compose.yml
services:
basement:
image: ghcr.io/mybsmt/basement:latest
container_name: basement
restart: unless-stopped
ports: ["8484:8484", "8484:8484/udp"]
environment:
TZ: America/New_York
TMDB_API_KEY: your-key-here
volumes:
- ./data:/data
- /srv/media:/media:ro
docker compose up -d
docker compose logs -f basement # "Basement 0.1.0 listening on http://0.0.0.0:8484"
Inside the container your media is under /media, so a library folder is /media/Movies.
The container runs as user 1000. If your data folder belongs to someone else, either
chown -R 1000:1000 ./data or add user: "<uid>:<gid>" to the service.
Hardware transcoding
Intel Quick Sync or AMD (VAAPI):
devices: ["/dev/dri:/dev/dri"]
group_add: ["109"] # the host's render group: getent group render | cut -d: -f3
NVIDIA (install the NVIDIA Container Toolkit first):
deploy:
resources:
reservations:
devices: [{ driver: nvidia, count: all, capabilities: [gpu, video] }]
Check Admin → Transcoding: it lists the encoders that passed a test encode on startup, and for each one the HDR tone mapper that passed a test tone map (for example "GPU, Intel Quick Sync" or "CPU, tonemapx"). If it says "none found", the GPU is not visible inside the container.
NVIDIA's consumer drivers allow 8 encodes at once, so Basement sends the 9th and later transcodes
to the CPU (the plan says "The GPU is at its session limit"). Set BASEMENT_MAX_GPU_SESSIONS to
change the limit for any GPU, for example to keep a weak iGPU from being overloaded. A CPU tone
mapper next to a GPU encoder usually means the GPU's driver lacks tone mapping support; HDR
transcodes still work, but each one uses much more CPU.
New files show up by themselves
Admin → Settings has the same three switches as Plex's library settings:
- Scan my library automatically: watch library folders and scan about 10 seconds after files stop changing.
- Run a partial scan when changes are detected: scan only the folders that changed.
- Scan my library periodically: a full scan of every library every 1, 2, 6 (default), 12 or 24 hours, or never. Admin → Jobs → Scheduled tasks shows the next run, and Run now starts one.
Two cases need help:
- Network shares (SMB, NFS) and some Docker bind mounts from a NAS usually send no change events.
The periodic scan picks new files up; press Scan now on the library to see them straight away.
BASEMENT_WATCH=0turns watching off for the whole server. - Very large libraries on Linux can run out of inotify watches (one per folder). Raise the limit
on the host:
echo fs.inotify.max_user_watches=524288 | sudo tee /etc/sysctl.d/90-basement.conf && sudo sysctl --system.
Libraries on a NAS
Basement reads a NAS through a folder the operating system has mounted, the same way Plex does. Mount the share so it comes back after a reboot, then add the library with Browse… in Admin → Libraries (shares are marked NAS there).
macOS: in Finder, Go → Connect to Server (⌘K), enter
smb://nas.local/Movies, and tick Remember this password. Then System Settings → General → Login Items → add the mounted share (drag it from the Finder sidebar) so it reconnects at login. It appears under/Volumes/Movies.Linux: add a line to
/etc/fstaband runsudo mount -a://nas.local/Movies /mnt/movies cifs credentials=/etc/nas-creds,uid=1000,gid=1000,ro,_netdev,nofail 0 0 nas.local:/volume1/tv /mnt/tv nfs ro,_netdev,nofail 0 0/etc/nas-credsholdsusername=andpassword=lines;chmod 600it.Docker: mount the share on the host as above, then bind it into the container read-only (
- /mnt/movies:/media/Movies:ro). Start the container after the mount (_netdevplusrestart: unless-stoppedhandles a slow NAS at boot).
If the share is not mounted when a scan runs, the library folder is missing or empty; Basement treats it as unmounted and keeps your items and watch history.
Faster transcodes with RAM
Temporary segments are written to /data/transcode and deleted when playback stops. To keep them
in memory instead (plan on about 2 GB per simultaneous 4K transcode):
tmpfs:
- /data/transcode:size=4g
Standalone binary
Download the archive for your platform from the releases page, check it, unpack it and run it:
sha256sum -c SHA256SUMS --ignore-missing
tar xzf basement-0.1.0-linux-x64.tar.gz
cd basement-0.1.0-linux-x64
./basement
FFmpeg is not bundled with the binary. Install it first:
| System | Command |
|---|---|
| Debian, Ubuntu | sudo apt install ffmpeg (or Jellyfin's jellyfin-ffmpeg7 for better hardware support) |
| Fedora | sudo dnf install ffmpeg (RPM Fusion) |
| macOS | Jellyfin's portable build (see below), or brew install ffmpeg |
| Windows | winget install Gyan.FFmpeg |
If FFmpeg is somewhere unusual, set BASEMENT_FFMPEG and BASEMENT_FFPROBE to its full paths.
HDR to SDR conversion needs an FFmpeg with zscale or a GPU tone mapper. Homebrew's FFmpeg has
neither, so HDR films play with washed-out colours on SDR screens (Admin → Transcoding says "HDR tone
map: unavailable"). On a Mac, use Jellyfin's portable build instead: download
jellyfin-ffmpeg_<version>_portable_macarm64-gpl.tar.xz (or mac64 on Intel) from
jellyfin-ffmpeg releases, unpack it, and
point BASEMENT_FFMPEG and BASEMENT_FFPROBE at the two files inside. It tone maps on the GPU with
VideoToolbox.
On macOS the binary is not notarized yet. The first time, run
xattr -d com.apple.quarantine ./basement or allow it under System Settings → Privacy & Security.
Run it as a service (Linux)
# /etc/systemd/system/basement.service
[Unit]
Description=Basement media server
After=network-online.target
Wants=network-online.target
[Service]
User=basement
Group=basement
SupplementaryGroups=render video
Environment=BASEMENT_DATA_DIR=/var/lib/basement
Environment=TMDB_API_KEY=your-key-here
ExecStart=/opt/basement/basement
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/basement
ProtectHome=read-only
[Install]
WantedBy=multi-user.target
sudo useradd --system --home /var/lib/basement --create-home basement
sudo tar xzf basement-0.1.0-linux-x64.tar.gz -C /opt && sudo mv /opt/basement-0.1.0-linux-x64 /opt/basement
sudo systemctl daemon-reload && sudo systemctl enable --now basement
journalctl -u basement -f
Settings
All settings are environment variables.
| Variable | Default | What it does |
|---|---|---|
PORT |
8484 |
HTTP port |
BASEMENT_HOST |
0.0.0.0 |
Interface to listen on (127.0.0.1 behind a reverse proxy on the same machine) |
BASEMENT_DATA_DIR |
./data (/data in Docker) |
Database, signing key, artwork, backups, transcode temp |
TMDB_API_KEY |
the built-in key | Your own TMDB key (v3 key or v4 token). Overrides Admin → Settings → TMDB and the key built into release binaries |
BASEMENT_FFMPEG, BASEMENT_FFPROBE |
ffmpeg, ffprobe on the PATH |
FFmpeg location |
BASEMENT_WHISPER |
the included whisper/whisper-cli beside the server, else on the PATH or in Homebrew's folders (/app/whisper/whisper-cli in Docker) |
whisper.cpp for generated subtitles (REQ-228) |
BASEMENT_WHISPER_MODEL |
the model downloaded in Admin → Settings | A whisper.cpp ggml model file to use instead |
BASEMENT_LAN_SUBNETS |
private ranges | Which client addresses count as "at home" (remote streams get the per-user bitrate limit) |
BASEMENT_DOWNMIX |
dave750 |
How surround becomes stereo: default, dave750 (clear dialogue) or nightmode |
BASEMENT_WEB_DIR |
the web folder beside the binary |
Web app to serve; set it to an empty value for API only |
BASEMENT_PUBLIC_URL |
none | The address people use to reach Basement, for links in emails |
BASEMENT_WATCH |
on | 0 turns library folder watching off for the server (the rest of the scanning settings are in Admin → Settings) |
BASEMENT_DISCOVERY |
on | 0 stops the Basement apps finding this server on your network by themselves (Bonjour/mDNS and UDP port 8484) |
BASEMENT_LOG_FILE |
installers: ~/Library/Logs/Basement/basement.log on macOS, logs\basement.log in the data folder on Windows |
Also write the log to this file (10 MB, then it moves to basement.log.1); an empty value turns it off. Docker and systemd log to the console |
BASEMENT_OTEL_ENDPOINT |
none | An OpenTelemetry collector (OTLP over HTTP, e.g. http://localhost:4318) to send request and job traces to; nothing is sent when unset |
BASEMENT_JELLYFIN |
on | 0 turns off the Jellyfin-compatible address (/jellyfin) that Jellyfin apps use |
Finding the server from the apps
At home, the Basement apps for phones and TVs find the server by themselves: it announces itself with Bonjour (mDNS) and answers on UDP port 8484. Nothing is sent outside your network. If an app cannot see it:
- Docker: Bonjour does not cross Docker's network. Use
network_mode: hoston Linux, or type the address in the app (http://<server>:8484). Publish8484:8484/udpfor Roku. - Firewall: allow UDP 5353 (Bonjour) and UDP 8484 as well as TCP 8484. The Windows installer's rule already covers them.
- Guest Wi-Fi or "client isolation" on the router keeps devices from seeing each other.
Roku
The Roku app isn't in the Roku Channel Store yet. To try it, put the Roku in developer mode
(on the remote: Home three times, Up twice, Right, Left, Right, Left, Right), set a password, then
open http://<roku address> in a browser and upload basement-roku.zip (build it with
pnpm --filter @basement/roku build). See apps/roku/README.md.
Coming from Plex, Jellyfin or Emby
Basement can bring over what everyone has watched and where they stopped. Point Basement's libraries at the same media folders first and let the scan finish, then go to Admin → Import:
- Jellyfin or Emby: the server's address (for example
http://192.168.1.20:8096) and an API key (Jellyfin: Dashboard → API Keys; Emby: Settings → Advanced → API Keys). - Plex: the server's address (
http://192.168.1.20:32400) and the owner's Plex token. To find it, sign in to Plex Web as the owner, open any title's ⋯ → Get Info → View XML, and copy theX-Plex-Tokenvalue from the address bar.
Basement reads everything and shows you each account, how many of its titles it found, and any titles that match more than one here (you pick). Then choose where each account's history goes: an existing Basement user, a new account (you get an invite link to send; their history is waiting when they sign up), or nowhere. Nothing changes until you press Import, the key is not kept, and importing again later only adds what is new.
Titles are matched by their TMDB, IMDb or TVDB ids, then by file name, then by title and year, so the other server can see your files at different paths. Plex only gives resume points for the owner's account; other Plex accounts bring what they finished watching. Ratings, favourites, collections and playlists are not brought over yet.
Using Jellyfin apps
Apps made for Jellyfin can use Basement too: Jellyfin for Android and Android TV, Swiftfin (iPhone, iPad, Apple TV), Infuse and others. In the app, add a server with this address:
http://<server>:8484/jellyfin
and sign in with your Basement username and password. They browse your libraries, play (direct or transcoded, the same as the Basement apps), resume where you stopped, and mark things watched.
- Profiles: signing in with your username uses your main profile. To use another one, sign in
as
username/Profile name(for examplerandy/Kids). If the profile has a PIN, put it on the end after a colon:randy:1234orrandy/Kids:1234. The password goes in the password box as usual. - Not there yet: favourites, music, live TV, collections, Quick Connect, and controlling one Jellyfin app from another.
- Away from home: use the same
/jellyfinpath on your remote address, for examplehttps://media.example.com/jellyfin. - Signing out: signing out in the app ends that sign-in. To turn the Jellyfin address off
completely, set
BASEMENT_JELLYFIN=0.
Watching away from home
Built-in remote access
Admin → Remote access → Turn on remote access. Basement then:
- listens for HTTPS on port 8443 (its own certificate, or yours: put
custom.crtandcustom.keyindata/certs/); - asks your router to forward a port to it with UPnP or NAT-PMP, and keeps renewing that while it runs (it removes the mapping when it stops). If your router has neither turned on, choose "I forwarded it myself", forward TCP 8443 to this computer, and enter your DNS name or public IP.
The page shows the address to use from outside and warns when your router's own internet address is private (a second router, or carrier-grade NAT from your ISP); a port mapping cannot reach the internet then, and a relay (planned, and one you can run yourself) is the answer. Check from a phone on mobile data, not your Wi-Fi. Port 8484 stays plain HTTP for your home network; never forward it. A brand-new server can only be set up from home, never through the remote port.
Browsers warn about Basement's own certificate, and the phone app can't use it at all: iOS and
Android only accept certificates from a real certificate authority, for the app and its video player
alike. For the phone app away from home, give the remote port a real certificate (put it in
data/certs/ as custom.crt and custom.key), or use Tailscale or a reverse proxy (below).
Your own domain (optional, free certificate from Let's Encrypt)
You don't need a domain: Tailscale (below) or a reverse proxy work without one. With your own
domain, people reach Basement at an address like https://media.example.com, with a real
certificate that browsers and the phone app trust, and nothing to install on their side.
- Buy a domain from any registrar, and use whichever DNS provider you like (the registrar's own is fine).
- At your DNS provider, add an A record for the name you want (
media, say) pointing at your public IP. Admin → Remote access shows it once the router port is open; or search "what is my IP" from home. - Admin → Remote access: turn on remote access (port 8443, or set the router port to 443 for an
address without a port number). Under Your own domain, enter
media.example.com, an email if you'd like Let's Encrypt to warn you about problems, tick the subscriber agreement, and save.
Basement then asks Let's Encrypt for a certificate. To prove the domain is yours, Let's Encrypt fetches a file over plain HTTP on port 80, so for that minute the router's port 80 leads to Basement's port 8480, which answers that one request and nothing else. With automatic port mapping Basement opens and closes port 80 itself; if you forwarded ports yourself, forward TCP 80 to this computer's port 8480 as well. The certificate renews itself about a month before it expires. If it can't be issued, the page says why (often: the domain doesn't point at you yet, or your internet provider blocks port 80) and Basement tries again later.
Two things to know:
- Your public IP can change. Most home connections get a new one now and then, and the A record has to follow. Update it by hand when it does, or use your router's "dynamic DNS" setting or your DNS provider's updater. (Optional, later: if your DNS is at Cloudflare, you will be able to let Basement update the record itself with an API token. Nothing needs Cloudflare.)
- Carrier-grade NAT: if Remote access says your router is behind another NAT, no port on it reaches the internet, so neither the domain nor the certificate check can work. Tailscale (below) works anyway.
Reach Basement from anywhere with Cloudflare
If your domain is on Cloudflare, one API token lets Basement set up remote access for you, in one of two ways:
- Tunnel (no open ports): Basement runs Cloudflare's
cloudflared(bundled) and your name reaches the server through Cloudflare. Nothing to forward on your router. - DNS only: Basement keeps your name pointed at your home address and gets its certificate through Cloudflare, so no port 80 is needed. Traffic goes straight to your server, so you still need a port forward or UPnP (Built-in remote access, above).
What you need: a domain whose DNS is on Cloudflare (a free account is enough), and a name
under it for Basement, such as basement.example.com.
Make the token. Basement needs these permissions:
| Policy | Permission | Why |
|---|---|---|
| Zone (your domain) | DNS: Edit | point the name at your server or tunnel |
| Zone (your domain) | Zone: Read | list your domain in Basement |
| Account | Cloudflare Tunnel: Edit | Tunnel mode only: make and run the tunnel |
| Account | Access: Apps and Policies: Read | optional: warns you when Cloudflare Access would stop the apps |
In the Cloudflare dashboard:
- Open My Profile → API Tokens (
dash.cloudflare.com/profile/api-tokens) and press Create Token. - Token name: anything, such as
Basement remote access. - Policies: choose Start from scratch → Start with a Zone policy (not the Edit zone DNS
template, which has only one of the permissions).
- Change All zones to Specific zone and pick your domain.
- Open DNS & Zones and tick DNS → Edit and Zone → Read.
- Close the policy with × (it stays, shown as "Zone policy … DNS Write, Zone Read").
- Add new policy → Add new Account policy.
- Pick your account instead of All accounts.
- Search the permissions for
Tunneland tick Cloudflare Tunnel → Edit (skip it if you'll only use DNS only). - Optionally search
Apps and Policiesand tick Access: Apps and Policies → Read. - Close the policy with ×.
- Token expiration: No expiration is simplest. If you pick a date, remote access stops then until you paste a new token.
- Client IP address filtering: leave it empty. The calls come from your home address, which can change, and a filter would then lock Basement out.
- Continue to summary. It should show your domain with DNS: Edit, Zone: Read and your account with Cloudflare Tunnel: Edit (and Access: Apps and Policies: Read if you added it). Press Create Token and copy the token: Cloudflare shows it only once.
Basement saves the token encrypted and never shows it again.
Tunnel. Admin → Remote access → Cloudflare: paste the token, press Check, pick the domain, type the name, choose Tunnel, then:
- New tunnel (easiest): Basement makes a tunnel, points the name at it and runs
cloudflareditself. Turning it off removes what it made. - A tunnel you already have: pick it from the list. If it is managed from the Cloudflare
dashboard, Basement adds one rule for the name and leaves your other rules alone; your own
cloudflaredkeeps running it. If it is run from aconfig.yml, Basement can't edit that file: the card shows the exact rule to paste underingress:(above the final catch-all), then restart thatcloudflared. If yourcloudflaredruns on another machine, set Listen on to Home network (connector on another computer) on the card and enter the connector computer's address: Basement then listens on your home network for it (pointing the rule at this computer's LAN address on the connector's subnet) and trusts the visitor addresses Cloudflare passes on only from that computer.
DNS only. Choose DNS only instead. Basement keeps an unproxied A record at your public IPv4 address (from your router's port mapping, else as Cloudflare sees it), plus an AAAA record only when it also finds a public IPv6 address. The own-domain certificate (above) is then issued without port 80: saving DNS only fills in Remote access's own domain with the name if it's empty (the card warns when it's set to another name). Remote access must be on and Let's Encrypt's terms agreed in the Remote access card.
Trade-offs, before you choose Tunnel:
- Cloudflare decrypts tunnel traffic at its edge, so it can see what passes through. The relay and DNS only do not have that property. The apps still check the server's signed identity.
- Cloudflare's free terms reserve heavy video delivery for its paid video products. A few friends streaming is usually tolerated, but accounts have been flagged. Set a remote bitrate limit for friends (Admin → Users).
- Friends sign in with their Basement accounts. Don't put Cloudflare Access in front of the name: it blocks the TV, Roku, Cast and Jellyfin apps.
Troubleshooting. The card shows a status message:
- Token not accepted / invalid: paste the token again; check it is not expired and has the permissions above.
- No zone for that name / name outside the zone: the name must be under a domain the token can see.
- A record was not made by Basement / your tunnel already sends the name elsewhere: something else owns that name. Basement never overwrites it; delete it in Cloudflare or pick another name.
- Cloudflare can't reach Basement yet: the connector isn't running or connected. For a
tunnel Basement runs, check Admin → Logs for
cloudflared; it retries after 1 s, 5 s, 30 s, then every 5 minutes. For your own, check it is running and has the rule. - Cloudflare Access is in front of this name: remove the Access application for the name.
- That name answers as another server: the name points at something else; pick another name.
- Rate limited: Cloudflare asked Basement to slow down; it tries again by itself.
The phone app away from home
Sign in once at home: the app asks the server for every address it can be reached at and remembers them. After that, each time it opens (or comes back after a while in the background) it tries your home addresses first, then, after a second and a half, the internet ones, and uses the first that proves it is your server. You stay signed in when it switches. Addresses you typed yourself (a reverse proxy's, say) are kept too. If nothing answers, the app says which addresses it tried.
Through a relay (when the router can't be opened)
Some connections can't be reached from outside however the router is set: carrier-grade NAT (common on mobile broadband and some fibre), a second router you don't control. A Basement Relay gets around that. The server keeps a connection out to the relay, and the apps reach it through the relay when nothing direct answers. The relay passes connections on still encrypted, and the certificate's key stays on your server. Whoever runs the relay also controls its domain, so they could get their own certificate for your relay name and read what passes through; use a relay you run yourself or trust.
Run your own on any small machine with a public address and a domain:
- Point
*.relay.example.comat it (a wildcard A record), and open TCP ports 443, 80 and 7443. - Download
basement-relayfor its system from the release page and runbasement-relay -domain relay.example.com. It prints a SHA-256 when it starts. - In Basement: Admin → Remote access → Relay. Enter
relay.example.com:7443and that SHA-256, agree to Let's Encrypt's terms if you haven't, and choose Use this relay.
Basement connects and gets a certificate for <server id>.relay.example.com; the page shows that
address once it's ready. Open the phone app at home once so it learns it. Away from home, it tries
your direct addresses first and the relay last. The relay's options (limits, an allow-list of
servers) are in apps/relay/README.md.
Tailscale (free, nothing opened on your router)
Tailscale connects your devices privately, wherever they are. Its personal plan is free for non-commercial use. Every device that watches needs the Tailscale app, signed in to your tailnet (or with the Basement machine shared to it): there are apps for iPhone, iPad, Android, Apple TV and Android TV, but not Roku or most smart TVs.
- Install Tailscale on the Basement computer and sign in. In the admin console, turn on MagicDNS and HTTPS Certificates (DNS page).
- On the Basement computer, run
tailscale serve --bg 8484. Tailscale now answershttps://<machine>.<tailnet>.ts.netwith its own trusted certificate and passes requests to Basement.tailscale serve statusshows the address. - On the phone, install Tailscale, sign in, and in the Basement app type that address once (or sign in at home: the app keeps every address it has used).
You don't need Basement's own remote access for this, so port 8443 stays free for your own domain
and you can use both. Basement sees Tailscale viewers as coming from the computer itself, so they
count as at home: the remote bitrate limit doesn't apply to them. To stop, run
tailscale serve --https=443 off.
Your own reverse proxy
Or put Basement behind a reverse proxy with HTTPS instead (never open port 8484 to the internet). Caddy does certificates for you:
media.example.com {
reverse_proxy 127.0.0.1:8484 {
flush_interval -1
}
}
flush_interval -1 stops Caddy from buffering, so the admin "Now playing" view stays live. With
nginx, set proxy_buffering off; on the location. Then set BASEMENT_LAN_SUBNETS to your home
network so remote viewers get the remote bitrate limits.
Upgrading
Admin shows a banner when a newer version is out, with the download for your kind of install
(or the docker pull command). Turn the daily check off in Admin → Settings → Updates.
Stop Basement, replace the binary (or docker compose pull && docker compose up -d), start it.
On startup Basement copies the database to data/backups/ before running any migration and puts
the copy back if a migration fails, so a failed upgrade leaves you on the old version with your
data intact. It keeps the last five backups.
To see what an upgrade will do first:
./basement migrate --check # or: docker exec basement /app/basement migrate --check
TMDB (artwork and Discover)
Release binaries and the Docker image come with a TMDB key built in, so artwork, matching and
Discover work straight away, as in Jellyseerr. To use your own (free from
themoviedb.org → Settings → API), paste it in Admin → Settings → TMDB, or set TMDB_API_KEY
(in the environment, or a .env file next to where you start Basement; see .env.example).
Running from source has no built-in key, so set one of those.
Load TMDB posters through this server (on by default) keeps browsers from contacting TMDB:
each poster is downloaded once into data/cache/tmdb and served from there.
Subtitles for files that have none
Basement can make subtitles from what is said in a film or episode, on your own server, with whisper.cpp. Nothing is sent anywhere for that.
- The installers, the release archives and the Docker image all include whisper.cpp. If you
run Basement from source, install it on the server machine (on a Mac,
brew install whisper-cpp; on Linux, build it from its repository) and putwhisper-clion the PATH, or setBASEMENT_WHISPERto it. On an Apple silicon Mac the included build uses the GPU. - In Admin → Settings → Generated subtitles, turn it on and download a speech model. Small (466 MB) is a good balance; on an Apple silicon Mac or with a fast GPU, Large v3 Turbo is the most accurate. A two-hour film takes a few minutes to half an hour, depending on the model and the machine.
- On a title's page, Make subtitles… makes them for that film or episode; on a show or season, Make subtitles for every episode. Or tick Make them automatically to have Basement make them for every file without subtitles in the languages you want.
They show up in every app's subtitle list as "Generated". With an AI key set for Ask Igor, they can also be translated into other languages ("Translated from English"); that sends the subtitle text, and nothing else, to OpenAI or Anthropic, and uses about 15,000 tokens for a two-hour film.
Ebooks and comics
Add a library of type Ebooks (EPUB and PDF) or Comics (CBZ, CBR, CB7 and PDF) under Admin → Libraries, then read in the browser: EPUBs reflow to the window, PDFs and comics show page by page, and your place is kept per profile, so you can carry on from another device.
- Names: Basement reads titles, authors and series from the files themselves (an EPUB's own
details, a comic's
ComicInfo.xml). Otherwise it goes by folders and names:Author/Title.epub,Author/Series/2 - Title.epub, or for comicsSaga/Saga 012 (2013).cbz. - Covers: an image beside the file with the same name (
Title.jpg), orcover.jpgin a folder holding only that book; otherwise the EPUB's cover or a comic's first page. - CBR and CB7 comics are read with
bsdtar(libarchive). macOS, Windows and the Docker image have it, and the Linux packages recommend it; if it's missing, install it (sudo apt install libarchive-toolson Debian and Ubuntu,sudo dnf install bsdtaron Fedora). Without it those comics are listed but can't be opened.
Requests with Radarr and Sonarr
People ask for titles on Discover (needs TMDB_API_KEY); you approve them on Requests.
To have approved requests download by themselves, connect Radarr and Sonarr in Admin → Settings →
Radarr and Sonarr:
- Enter the address (for example
http://localhost:7878) and the API key from Radarr's Settings → General, then Test connection. - Pick the quality profile and root folder new titles should use. Tick 4K for a second instance that handles 4K requests.
- If Radarr runs in a different container and sees your media under another path, add a mapping
per line, such as
/movies => /media/Movies. - After Add, Basement shows a webhook URL once. In Radarr: Settings → Connect → + → Webhook, tick On Import and On Upgrade, and paste it. New downloads then appear within seconds instead of at the next scan. Lost it? New webhook URL makes another (the old one stops working).
Requests approved before you connected anything are sent as soon as you add the connection (and Admin → Jobs → Scheduled tasks retries any that are still waiting every 15 minutes). A request that failed has Send again on the Requests page.
Basement never downloads anything itself; it only hands requests to the apps you connect.
Notifications
Admin → Settings has two ways to hear about requests:
- Notifications: a Discord channel (paste the webhook URL from Server Settings → Integrations →
Webhooks) or any service that takes a webhook, such as Home Assistant, ntfy or n8n. Webhooks are
signed:
Basement-Signature: t=<unix>,v1=<hex>is the HMAC-SHA256 oft.bodywith the secret shown when you add it. Addresses on your own network are refused unless you tick allow private network addresses. - Email: enter your mail provider's SMTP details (Gmail, Fastmail and others want an app
password) and send a test. People then add their address on the Requests page and confirm it
with the code they are emailed (or the link, when
BASEMENT_PUBLIC_URLis set); they hear when their requests are approved, declined or ready, and admins with an address hear about new requests waiting for approval. SetBASEMENT_PUBLIC_URLto the address people use (for examplehttps://basement.example.com) to put links in emails; without it, emails carry only codes.
Backing up
Everything that matters is in the data folder: basement.db (users, watch history, libraries),
keys/ (the token signing key and secrets.key, which encrypts Radarr/Sonarr API keys; without it they must be re-entered), images/ (artwork) and subtitles/ (extracted tracks, which can
be rebuilt). Stop Basement and copy the folder; copying basement.db while the server runs can
catch it mid-write. transcode/ is temporary and safe to skip.
Forgot your password?
Click Forgot password? on the sign-in screen (web, phone or TV) and enter your username or
email. If your account has a confirmed email (Requests → email news) and the server has email set
up (Admin → Settings → Email), Basement sends a code, and a link too when BASEMENT_PUBLIC_URL is
set. Either works for an hour. Changing the password signs you out on every device and revokes
your API keys.
Without email, the owner can set anyone's password in Admin → Users. If you are the owner and can't sign in, run this on the computer running Basement (it asks for the new password twice and shows nothing as you type):
/Applications/Basement.app/Contents/MacOS/basement-server reset-password randy # Mac app
docker exec -it basement /app/basement reset-password randy # Docker
sudo -u basement basement reset-password randy --data-dir /var/lib/basement # Linux package
"%ProgramFiles%\Basement\basement.exe" reset-password randy # Windows
./basement reset-password randy # standalone
Basement can keep running. Pass --password-stdin to read the password from standard input
instead (for scripts).
Troubleshooting
- "The server is not answering" in the browser: check
docker compose logs basementorjournalctl -u basement; the first line after startup says where it is listening. - Everything transcodes: signed in as an admin, open the title and click Plays here as (under the version and track pickers). It names the exact reason (codec, bitrate limit, subtitle burn-in) for that device.
- A title shows "Not matched": no TMDB key is set, or the folder name did not match. Rename the
folder to
Title (Year)and scan again. - Logs: Admin → Logs shows recent warnings, failed jobs and FFmpeg errors. The Mac and
Windows installers also keep a log file:
~/Library/Logs/Basement/basement.log(open it in Console.app) or%LOCALAPPDATA%\Basement\logs\basement.log. - Something is wrong and you don't know what: Admin → Library health starts with the
server checks (database, data folder, FFmpeg, every library folder, background jobs), each with
what to do. On the server machine,
basement diagnoseprints the same checks when the web app won't load (/Applications/Basement.app/Contents/MacOS/basement-server diagnoseon a Mac,docker exec basement /app/basement diagnosein Docker). - Reporting a problem: Admin → Diagnostics → Download a report (or
basement diagnose --out) makes a zip with logs, versions and checks. Keys, tokens and passwords are taken out, so you can attach it to a GitHub issue.