One docker compose up and S1 is running on your box.
No SaaS in the middle, no egress bills, no metered viewers. Pull
the latest build whenever you want; your cameras, users, and share
links stay put.
A quiet 15 minutes, one machine, and line of sight to your cameras.
3000; you can remap it.pull, not apt-get.
Two commands on a fresh Ubuntu/Debian box. Installs Docker, adds you to the docker group, writes .env with a fresh secret, and launches. Skip to Step 1 if you'd rather walk through it by hand.
sudo apt-get install -y git git clone https://github.com/networkyoda/s1.git s1 && cd s1 ./scripts/bootstrap-ubuntu.sh
The script prints the URL to browse to when it's done. It's re-runnable — each step is a no-op if already done.
Prefer one command at a time? The seven-command manual version:
# Docker — one line, official script curl -fsSL https://get.docker.com | sudo sh sudo usermod -aG docker $USER && newgrp docker # Get S1 and launch sudo apt-get install -y git git clone https://github.com/networkyoda/s1.git s1 && cd s1 cp .env.example .env sed -i "s|replace-me-with-a-long-random-string|$(openssl rand -hex 32)|" .env docker compose up -d
get.docker.com is maintained by Docker Inc., but it runs as root and adds an apt repo without letting you review first. Docker's own docs call it "not recommended for production." For a test box the convenience is worth it; for a hardened production host, use the manual apt-repo sequence from Docker's official docs and then follow Steps 1–4 below.
All the configuration you need is checked in — docker-compose.yml, the Dockerfile, and a template env file.
git clone https://github.com/networkyoda/s1.git s1 cd s1
.envOne required secret (the auth signing key), two optional knobs.
cp .env.example .env # Generate a random signing key and paste it in as JWT_SECRET: openssl rand -hex 32
Open .env in your editor. The shipped values are safe defaults except for JWT_SECRET — change that to the output of the openssl command above so auth tokens are signed with a key only you know.
.env. It's in .gitignore already, but double-check before you push a fork anywhere public.
Compose pulls the image, starts the container, and mounts a named volume for your SQLite database.
docker compose up -d docker compose logs -f
You should see s1 listening on 0.0.0.0:3000 within a second or two. Ctrl-C out of the log tail whenever you're satisfied.
ghcr.io/networkyoda/s1:latest and GHCR defaults to private. Either flip the package to public in the repo's Packages tab, or docker login ghcr.io with a GitHub PAT that has read:packages. While you're waiting, docker compose up -d --build builds the image locally from the Dockerfile in the repo.
The first user you register becomes the admin of that instance.
http://<host>:3000 — on the same machine, http://localhost:3000.user:pass@ credentials.Share/embed codes are generated from the per-stream Share menu. The embed is tap-to-play by design so an <iframe> on a page nobody reads doesn't pull bandwidth from your cameras.
GitHub Actions publishes a fresh image on every push to main. Pulling the latest takes about ten seconds of downtime.
cd /path/to/s1 git pull docker compose down docker compose pull docker compose up -d
Your s1-data volume — the one holding the SQLite database with users, streams, and share links — survives down and is re-attached to the new container. A clean wipe (if you ever want one) is docker compose down -v.
Every knob is an environment variable. See src/config.js for the source of truth.
| Variable | Default | Notes |
|---|---|---|
JWT_SECRET | dev-secret-change-me | Required. Any long random string; keep it stable across restarts. |
PORT | 3000 | Host port mapped to the container. |
TRUST_PROXY | 1 | false for direct LAN access, a hop count (or true) when behind a reverse proxy. |
HOST | 0.0.0.0 | Bind address inside the container. Rarely needs changing. |
DB_PATH | /data/rtspme.db | SQLite file path inside the container. The named volume lands at /data. |
FFMPEG_PATH | ffmpeg | Only set if you've got a custom ffmpeg build on PATH. |
MARKETING_HOME | false | Leave off for self-hosting — / serves the login screen. The hosted product sets this to true to surface the public landing page at /. |
The dashboard's per-stream Logs button is the first place to look; it tails the last few hundred ffmpeg lines per stream.
source video codec is hevc — using transcode on session start./healthz. If docker ps shows unhealthy, pull container logs with docker compose logs s1.