This page covers the full production-style setup for Remote Lab on a VPS.
1. Get a VPS
We recommend Hetzner Cloud.
| Plan | CX22 (2 vCPU, 4 GB RAM) |
| OS | Ubuntu 24.04 |
| Location | Closest to you |
Suggested regions:
| You're in | Choose |
|---|---|
| US East | Ashburn |
| US West | Hillsboro |
| Europe | Falkenstein, Nuremberg, or Helsinki |
| Asia | Singapore |
2. Point your domain
Add DNS A records pointing to your server IP:
lab.yourdomain.com → <server-ip>
docs.yourdomain.com → <server-ip>
Verify with:
dig +short lab.yourdomain.com
3. Secure the server
ssh root@<server-ip>
ufw default deny incoming
ufw allow 22
ufw allow 443
ufw enable
apt update && apt install -y fail2ban
4. Install dependencies
curl -LsSf https://astral.sh/uv/install.sh | sh
install -m 755 ~/.local/bin/uv /usr/local/bin/uv
curl -fsSL https://bun.sh/install | bash
apt install -y caddy ripgrep
source ~/.bashrc
# Symlink bun as node (needed for global bun packages whose shims use #!/usr/bin/env node)
ln -s /root/.bun/bin/bun /usr/local/bin/node
# Marp CLI for building slide decks
bun install -g @marp-team/marp-cli
5. Clone and install
cd /srv
git clone https://github.com/harangju/remote-lab.git
cd remote-lab
uv sync
cd frontend && bun install && bun run build && cd ..
6. Set permissions
The service runs as www-data.
sudo chown -R www-data:www-data /srv/remote-lab
sudo chown -R www-data:www-data /srv/projects
sudo chown -R www-data:www-data /var/www
Projects typically live under /srv/projects/. If files are later created by root or another user, the agent may lose write access. /var/www must also be writable by www-data so uv can create its cache under /var/www/.cache/uv.
7. Configure git for the agent
The agent needs a git identity and GitHub credentials to work with project repositories.
sudo chown www-data:www-data /var/www
sudo -u www-data git config --global user.name "Remote Lab Agent"
sudo -u www-data git config --global user.email "you@example.com"
If projects use HTTPS remotes, set up a credential helper so the agent can authenticate. With gh installed:
sudo -u www-data gh auth login
sudo -u www-data git config --global credential.https://github.com.helper '!/usr/bin/gh auth git-credential'
/var/www must be owned by www-data so git can write its global config and lock files there.
8. Allow the agent to restart itself
echo 'www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart remote-lab' | sudo tee /etc/sudoers.d/remote-lab
This allows the bash tool to restart the app after backend changes.
9. Configure environment
cp .env.example .env
Set at least:
WS_TOKEN=<generate with: openssl rand -hex 32>
ALLOWED_ORIGIN=https://lab.yourdomain.com
PUBLIC_BASE_URL=https://docs.yourdomain.com
ANTHROPIC_API_KEY=sk-ant-...
DEEPGRAM_API_KEY=<your Deepgram API key> # optional, enables voice input
OPENAI_API_KEY=sk-... # optional
GOOGLE_API_KEY=AI... # optional
PUBLIC_BASE_URL is used by /share and /shares to report published URLs on the docs/public site instead of the lab app origin.
If DEEPGRAM_API_KEY is configured, users can start voice input from the chat composer. Browser microphone audio is streamed to the backend, proxied to Deepgram's realtime API (nova-3), and partial/final transcripts are inserted into the draft without auto-sending.
10. Set up Caddy
Edit /etc/caddy/Caddyfile:
lab.yourdomain.com {
reverse_proxy localhost:3000
}
docs.yourdomain.com {
reverse_proxy localhost:3001
}
Then reload:
systemctl reload caddy
11. Create systemd services
/etc/systemd/system/remote-lab.service:
[Unit]
Description=remote-lab
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/srv/remote-lab
EnvironmentFile=/srv/remote-lab/.env
ExecStart=/usr/local/bin/uv run uvicorn backend.server:app --host 127.0.0.1 --port 3000
Restart=always
[Install]
WantedBy=multi-user.target
/etc/systemd/system/remote-lab-docs.service:
[Unit]
Description=remote-lab-docs
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/srv/remote-lab
EnvironmentFile=/srv/remote-lab/.env
ExecStart=/usr/local/bin/uv run uvicorn backend.docs_server:app --host 127.0.0.1 --port 3001
Restart=always
[Install]
WantedBy=multi-user.target
Start them:
systemctl daemon-reload
systemctl enable --now remote-lab
systemctl enable --now remote-lab-docs
12. Verify
Open https://lab.yourdomain.com and enter your token.
systemctl status remote-lab
journalctl -u remote-lab -f