Skip to content

Deploy to a machine

Put a stream on a server or a small box by the door, keep it running without anyone logged in, update it, and manage it remotely.

There’s no separate deployment system in Tatolab. A machine runs its own runtime, and a stream you keep with tatolab run -d stays running and comes back after every restart. Deploying a stream means getting the project onto the machine and keeping it there.

This page assumes a Linux machine you reach over ssh: a server, a small box next to a camera, or a cloud machine with a GPU.

On the machine:

Terminal window
curl -fsSL https://get.tatolab.com/install.sh | sh

When the installer offers to turn on lingering, say yes. Without it, the runtime runs only while someone is logged in; with it, the runtime starts at boot. You can also turn it on later with loginctl enable-linger.

Check it:

Terminal window
tatolab streams

See Install on Linux for requirements and details.

A project is just files, so use what you already use: git clone, rsync, scp. Leave .venv/ out; it belongs to the machine it was made on.

Terminal window
git clone https://github.com/your-org/door-camera.git
cd door-camera
uv sync

uv sync builds the project’s .venv on this machine, from your pyproject.toml and uv.lock. Commit uv.lock so the machine gets exactly the versions you tested with.

If a node needs files that aren’t in the repository, such as model weights, put them in place now, so the node doesn’t download them while it starts.

Terminal window
tatolab run -d
Output
door_camera kept (project /home/ops/door-camera)

That’s the deployment. The runtime loads the stream now and every time it starts again. To check it survives a reboot, reboot and run tatolab streams.

A server has no desktop, so plan the stream for that:

  • Leave out DisplayWindow. With no display, there’s nowhere for a window to open.
  • See frames with exchange instead. For any output something reads, tatolab exchange --channel door_camera/camera/video --out snapshots/ saves full-resolution PNGs you can copy off the machine.
  • Expose outputs for other streams and agents on the machine, or record with Mp4Sink. A kept stream starts its recording over each time it reloads, after every reboot, so record from a separate stream, or copy files off; see Recordings and restarts.
  • Audio: with no sound devices, MicrophoneSource produces silence and SpeakerSink discards its input, so the stream still runs.

A machine with several cameras needs to say which one. On Linux, list them with v4l2-ctl --list-devices (from the v4l-utils package), then set device_id:

camera = stream_builder.add(CameraSource, config={"device_id": "/dev/video2", "max_width": 1280, "max_height": 720})

A device_id that can’t be opened is refused by name when the stream starts, instead of silently falling back to another camera. /dev/video numbers can change when cameras are plugged in a different order; the stable names under /dev/v4l/by-id/ don’t.

Terminal window
cd door-camera
git pull
uv sync
tatolab run -d

Running tatolab run -d again for the same project and stream replaces the kept record with the new version.

Every tatolab command works over ssh. A command run over ssh doesn’t get ~/.local/bin on its PATH on Debian and Ubuntu, so give the path where the installer put tatolab, relative to your home folder there:

Terminal window
ssh door-box .local/bin/tatolab streams
ssh door-box .local/bin/tatolab logs --stream door_camera --level warn -f
ssh door-box .local/bin/tatolab stop door_camera

To let an agent manage the machine, run tatolab mcp over ssh:

Terminal window
claude mcp add door-box -- ssh door-box .local/bin/tatolab mcp

Control never travels over the network by itself: the runtime only listens on a local socket, and remote access is always ssh, with ssh’s keys and permissions.

If your machines are on a Tailscale tailnet, ssh to them by their tailnet names, from anywhere, without opening ports. The runtime also uses the machine’s tailnet name as its own name, the first part of every address. Tailscale isn’t required; without it, the machine is named after its hostname.

Where there’s no systemd, as in most containers, run tatolabd as the container’s main process. It runs in the foreground until it gets a stop signal. Giving the container the GPU and camera devices is your container runtime’s job (for NVIDIA GPUs, the NVIDIA Container Toolkit). A container that shares the host’s network counts as the same machine as the host, so only one of them can run a runtime.

  • tatolab streams shows every stream’s state. A kept stream that keeps crashing the runtime is marked failed, with the reason, and the runtime starts the others without it.
  • tatolab logs --stream <name> --level warn shows a stream’s problems; journalctl --user -u tatolabd shows the runtime’s own output.
  • tatolab graph shows whether frames are being dropped, and where.

Each machine runs its own runtime and its own streams. You deploy to each the same way. Streams on one machine read each other’s outputs, and streams on your other machines read public ones by URL, through the runtime’s MoQ relay. See Across machines.

  • Runtime installed, lingering on, tatolab streams answers
  • Project copied, uv sync run, model weights and other files in place
  • No DisplayWindow on a machine without a screen
  • Cameras chosen by stable device_id, recording paths absolute, and recordings safe from a reload
  • tatolab run -d, then a reboot, then tatolab streams shows it kept and running