Deploying Tabby Web with Portainer: Because My Configs Must Follow Me Everywhere

| Jul 10, 2025

Howdy!

Gather ‘round fellow homelab wranglers, because I’m about to share the saga of how I ended up deploying Tabby in Docker Compose—not because I had to, but because I’m an unrepentant configuration maximalist who wants his terminal settings to sync across every machine, every time, everywhere.

I’ve been living happily in Portainer for a while, clicking my little containers into place like a well-oiled IKEA flat-pack. As a long time Tabby user .it was just a matter of time ‘til I’d put my hands on Eugeny’s tabby-web, and everything changed.

Imagine a terminal backed by a self hosted service where:

  • Your connections and profiles live in a central database
  • You can log in with GitHub (or other providers)
  • Your settings follow you like a faithful config hound—no matter the device

This was it. Just a well tailored stack file was separating me from clicking “Deploy Stack” in Portainer.

Why Bother?

Let’s be honest—SSH works fine. So does a local Tabby app. But my brain had other plans:

“What if you could have the same terminal settings on your desktop, laptop, and even your phone—without exporting JSON blobs every time you tweak a color scheme?”

I couldn’t unhear it. I had to have config synchronization. One DB to rule them all. One volume to find them.

And so began the Portainer journey.

The Stack (or: How I Finally Got My Tabs in Sync)

Here’s the Compose file that became my new home base for terminal bliss:

version: "3.8"

services:
  db:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_DB: tabby
      POSTGRES_USER: tabby
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - /mnt/tabby/pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U tabby"]
      interval: 10s
      timeout: 5s
      retries: 5

  tabby:
    image: ghcr.io/eugeny/tabby-web:latest
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    ports:
      - "9090:8000"
    environment:
      DATABASE_URL: postgres://tabby:${POSTGRES_PASSWORD}@db:5432/tabby
      APP_DIST_STORAGE: "file:///data"
      SOCIAL_AUTH_GITHUB_KEY: ${SOCIAL_AUTH_GITHUB_KEY}
      SOCIAL_AUTH_GITHUB_SECRET: ${SOCIAL_AUTH_GITHUB_SECRET}
      DEBUG: ${DEBUG}
      HOST: ${HOST}
      PORT: ${PORT}
    volumes:
      - /mnt/tabby/data:/data
What This Actually Does

Postgres Service:

  • Stores all your configs, profiles, and connection settings.
  • Mounts to /mnt/tabby/pgdata so a reboot doesn’t vaporize your precious configs.
  • Has a healthcheck, because Tabby won’t tolerate a database still rubbing the sleep from its eyes.

Tabby Service

  • Runs the web app that serves up your terminal in any browser.

  • Maps port 8000 in the container to 9090 on your host—so you can hit http://your-server:9090 and be greeted by your familiar, synced environment.

  • Stores file-based bits (like downloaded assets) in /mnt/tabby/data.

  • Authenticates via GitHub, so you never have to remember another password. Just click and go.

Zero drama: just a Sweet, Sweet Result

Now, no matter where I log in—from my desktop, my laptop, or a random Chromebook at the in-laws’—I get:

  • The same color scheme
  • The same SSH profiles
  • The same configuration All synced. All persistent. All in one place. I finally have a single source of terminal truth! Of course my next step was to stick everything behind a Cloudflare tunnel and have it on the go.
Final Thoughts

Deploying tabby-web with Docker Compose was the best thing I’ve done for my terminal workflow in ages. If you’re tired of exporting config files, copying them across machines, and forgetting which version is current—do yourself a favor and set this up.

And if you, too, have made your dotfiles your personality, we should probably be friends.

Questions? Got your own container horror stories? Think I’m overcomplicating everything? Drop me a line. Misery—and synced configs—love company.

Happy building ⚒