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:

Either way, you can check the download against SHA256SUMS on the release page first.

What you need

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:

Two cases need help:

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).

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:

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:

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.

Watching away from home

Built-in remote access

Admin → Remote access → Turn on remote access. Basement then:

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.

  1. Buy a domain from any registrar, and use whichever DNS provider you like (the registrar's own is fine).
  2. 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.
  3. 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:

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:

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:

  1. Open My Profile → API Tokens (dash.cloudflare.com/profile/api-tokens) and press Create Token.
  2. Token name: anything, such as Basement remote access.
  3. 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").
  4. Add new policy → Add new Account policy.
    • Pick your account instead of All accounts.
    • Search the permissions for Tunnel and tick Cloudflare Tunnel → Edit (skip it if you'll only use DNS only).
    • Optionally search Apps and Policies and tick Access: Apps and Policies → Read.
    • Close the policy with ×.
  5. Token expiration: No expiration is simplest. If you pick a date, remote access stops then until you paste a new token.
  6. 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.
  7. 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:

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:

Troubleshooting. The card shows a status message:

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:

  1. Point *.relay.example.com at it (a wildcard A record), and open TCP ports 443, 80 and 7443.
  2. Download basement-relay for its system from the release page and run basement-relay -domain relay.example.com. It prints a SHA-256 when it starts.
  3. In Basement: Admin → Remote access → Relay. Enter relay.example.com:7443 and 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.

  1. Install Tailscale on the Basement computer and sign in. In the admin console, turn on MagicDNS and HTTPS Certificates (DNS page).
  2. On the Basement computer, run tailscale serve --bg 8484. Tailscale now answers https://<machine>.<tailnet>.ts.net with its own trusted certificate and passes requests to Basement. tailscale serve status shows the address.
  3. 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.

  1. 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 put whisper-cli on the PATH, or set BASEMENT_WHISPER to it. On an Apple silicon Mac the included build uses the GPU.
  2. 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.
  3. 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.

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:

  1. Enter the address (for example http://localhost:7878) and the API key from Radarr's Settings → General, then Test connection.
  2. Pick the quality profile and root folder new titles should use. Tick 4K for a second instance that handles 4K requests.
  3. If Radarr runs in a different container and sees your media under another path, add a mapping per line, such as /movies => /media/Movies.
  4. 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:

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