Self-Host Tabby Web with Docker Compose and GitHub OAuth

Deploy Tabby Web on Ubuntu with Docker Compose, MariaDB, GitHub OAuth, HTTPS, persistent storage, and configuration sync.

Tabby Web provides the Tabby terminal in a browser and can also synchronize Tabby desktop configurations. This guide deploys the official prebuilt image with Docker Compose, MariaDB, GitHub OAuth, persistent storage, and an HTTPS reverse proxy.

The upstream maintainer currently has limited time to support Tabby Web. The official container also retains an older runtime. Review the project status, test upgrades before production use, and avoid exposing an unmaintained deployment to sensitive networks without additional controls.

Official references:

Environment

The examples use the following values:

Setting Example
Server operating system Ubuntu 24.04 LTS
Public hostname tabby.example.com
Local Tabby Web address 127.0.0.1:9090
Installation directory /opt/tabby-web
Authentication provider GitHub OAuth

Create a public DNS record for tabby.example.com before configuring HTTPS. Allow inbound TCP ports 80 and 443 to the reverse proxy. The application port remains bound to localhost and should not be published directly to the Internet.

Install Docker Engine

If Docker Engine and the Docker Compose plugin are already installed, verify them and continue to the next section.

sudo docker version
sudo docker compose version

On a clean Ubuntu 24.04 LTS server, install Docker from Docker’s official repository.

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo docker run --rm hello-world

Membership in the docker group grants privileges equivalent to root. This guide keeps sudo in the Docker commands instead of changing group membership.

Register a GitHub OAuth App

Open GitHub Developer Settings, select OAuth Apps, and create a new OAuth app.

Use these values:

Field Value
Application name Tabby Web
Homepage URL https://tabby.example.com
Authorization callback URL https://tabby.example.com/api/1/auth/social/complete/github/

Copy the Client ID, generate a Client secret, and store both in a password manager. The callback URL must match exactly, including the /api/1/auth/social/complete/github/ path and trailing slash.

Create the Deployment Files

Create the project directory.

sudo install -d -m 0750 /opt/tabby-web
sudo chown "$USER":"$USER" /opt/tabby-web
cd /opt/tabby-web

Generate three hexadecimal secrets. Hexadecimal database passwords do not require URL encoding inside DATABASE_URL.

openssl rand -hex 32
openssl rand -hex 32
openssl rand -hex 32

Create /opt/tabby-web/.env and replace every placeholder with the appropriate generated secret or GitHub OAuth value.

nano /opt/tabby-web/.env
DB_PASSWORD=replace_with_the_first_generated_value
DB_ROOT_PASSWORD=replace_with_the_second_generated_value
DJANGO_SECRET_KEY=replace_with_the_third_generated_value
SOCIAL_AUTH_GITHUB_KEY=replace_with_the_github_client_id
SOCIAL_AUTH_GITHUB_SECRET=replace_with_the_github_client_secret

Restrict access to the secrets file.

chmod 600 /opt/tabby-web/.env

Create /opt/tabby-web/compose.yaml.

nano /opt/tabby-web/compose.yaml
services:
  db:
    image: mariadb:10.11
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: tabby
      MARIADB_USER: tabby
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: [CMD, healthcheck.sh, --connect, --innodb_initialized]
      interval: 10s
      timeout: 5s
      retries: 10

  tabby:
    image: ghcr.io/eugeny/tabby-web:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "127.0.0.1:9090:80"
    environment:
      DATABASE_URL: mysql://tabby:${DB_PASSWORD}@db:3306/tabby
      PORT: "80"
      DOCKERIZE_ARGS: -wait tcp://db:3306 -timeout 60s
      APP_DIST_STORAGE: file:///app-dist
      DJANGO_SECRET_KEY: ${DJANGO_SECRET_KEY}
      SOCIAL_AUTH_GITHUB_KEY: ${SOCIAL_AUTH_GITHUB_KEY}
      SOCIAL_AUTH_GITHUB_SECRET: ${SOCIAL_AUTH_GITHUB_SECRET}
    volumes:
      - app-dist:/app-dist

volumes:
  app-dist:
  db-data:

Do not add DEBUG=False to this deployment. The current upstream settings convert any nonempty DEBUG value to true, so the literal string False unexpectedly enables Django debug mode. Leaving the variable unset keeps debug mode disabled.

The original sample also used SQLite and exposed port 8000. The current official image listens on container port 80, and MariaDB is a better choice for a persistent multi-user deployment.

Validate the Compose file before starting the containers.

cd /opt/tabby-web
sudo docker compose config --quiet

The expanded output of docker compose config contains substituted secrets. Do not redirect or publish it.

Start Tabby Web

Pull the images and start the services.

cd /opt/tabby-web
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps

Check the application locally.

curl -fsS http://127.0.0.1:9090/api/1/auth/providers
sudo docker compose logs --tail=100 tabby

The providers endpoint should return JSON that includes GitHub. If it returns an empty list, recheck the two SOCIAL_AUTH_GITHUB_* values and recreate the application container.

sudo docker compose up -d --force-recreate tabby

Add a Tabby Web Application Version

The backend stores browser application distributions separately from the server image. Check the available versions of tabby-web-container, then add the selected version. The following version was available when this guide was updated.

cd /opt/tabby-web
sudo docker compose exec tabby /manage.sh add_version 1.0.231-nightly.0
sudo docker compose exec tabby ls -lah /app-dist

The app-dist volume preserves these files when the Tabby Web container is replaced.

Configure HTTPS with a Reverse Proxy

Tabby Web handles authentication tokens and synchronized terminal settings, so use HTTPS outside a local test environment. Keep port 9090 bound to 127.0.0.1 and proxy the public hostname to it.

For Caddy, add this site block to /etc/caddy/Caddyfile:

tabby.example.com {
    reverse_proxy 127.0.0.1:9090
}

Validate and reload Caddy.

sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Caddy obtains and renews the TLS certificate automatically when DNS is correct and TCP ports 80 and 443 reach the server. Other reverse proxies must preserve the Host, X-Forwarded-For, and X-Forwarded-Proto headers and support WebSocket upgrades.

Open https://tabby.example.com, select the login button, and authenticate with GitHub. If GitHub reports a callback mismatch, compare the configured callback URL character by character with:

https://tabby.example.com/api/1/auth/social/complete/github/

Configure Tabby Desktop Synchronization

After signing in to Tabby Web, open its settings and copy the Config sync token. Treat this token like a password.

Before the first synchronization, open Settings → Config file in the Tabby desktop app and create a backup. Then open Settings → Config Sync and enter:

Field Value
Sync host https://tabby.example.com
Secret sync token The token copied from Tabby Web

Do not add /api/1/ or a trailing API path to the sync host. Tabby adds its API paths automatically.

For the first device, choose the option that uploads the known-good local configuration as a new remote configuration. On additional devices, choose the intended remote configuration and verify the download direction before overwriting local settings. Enable automatic synchronization only after confirming that the correct configuration exists on both sides.

Optional: Browser SSH and Telnet Connections

Configuration synchronization does not require a connection gateway. Browser-based SSH and Telnet connections do require one because a browser cannot open arbitrary TCP connections directly.

Tabby Web can use the hosted gateway, but connection traffic then passes through that external service. For sensitive environments, deploy the separate Tabby Connection Gateway, protect it with a strong token, and publish it through HTTPS and WebSockets.

Back Up the Deployment

Back up both named volumes and the protected configuration files. A database backup is safer than copying a live database volume.

cd /opt/tabby-web
sudo docker compose exec -T db sh -c 'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" --single-transaction --databases tabby' > tabby-db.sql
sudo tar -C /opt/tabby-web -czf tabby-web-config.tar.gz .env compose.yaml

Also back up the app-dist volume or retain a record of every version added with manage.sh so it can be recreated.

Update Tabby Web

Back up the database and configuration first. Then pull the current images and recreate the services.

cd /opt/tabby-web
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=100 tabby

Because latest is a moving tag and the project has limited maintenance, test the update with a restored copy of the data before applying it to an important deployment. For reproducible production updates, replace latest with a tested image digest.

Troubleshooting

No Authentication Providers Are Shown

Check that the OAuth variables reached the application container without printing their values.

cd /opt/tabby-web
sudo docker compose exec tabby sh -c 'env | cut -d= -f1 | grep ^SOCIAL_AUTH_'
curl -fsS http://127.0.0.1:9090/api/1/auth/providers

GitHub Reports a Callback Error

The callback registered in GitHub must use HTTPS, the correct hostname, and the complete path /api/1/auth/social/complete/github/. Also verify that the reverse proxy sends X-Forwarded-Proto: https.

The Page Is Blank or an Application Version Is Missing

Inspect the persisted distribution directory and add a valid version from the npm package list.

cd /opt/tabby-web
sudo docker compose exec tabby ls -lah /app-dist
sudo docker compose logs --tail=200 tabby

The Database Is Unavailable

Check the database health and review both service logs.

cd /opt/tabby-web
sudo docker compose ps
sudo docker compose logs --tail=200 db
sudo docker compose logs --tail=200 tabby
sudo docker compose exec tabby python -c "import django; django.setup(); from django.db import connection; connection.ensure_connection(); print('Database connection succeeded')"

Configuration Sync Does Not Start

Confirm that the sync host contains only the HTTPS origin, copy a fresh token from the authenticated Tabby Web settings, and verify that the desktop clock is correct. Restore the local configuration backup if the wrong synchronization direction overwrites the device.

Licensed under CC BY-NC-SA 4.0
Last updated on Thursday, September 24, 2026
comments powered by Disqus