Skip to content

Upgrades and Troubleshooting

Keep your self-hosted Glassy updated with docker compose pull or auto-update with Watchtower, check container health, and diagnose common issues.

Video coming soon.

Before you start

  • self-hosting/01-deploying-with-docker

What you'll learn

  • Upgrade Glassy to the latest version
  • Set up automatic updates with Watchtower
  • Check container health and readiness
  • Debug from inside the container
  • Diagnose common startup and runtime issues

Keeping Glassy updated is simple — a two-command pull and recreate. This lesson covers upgrades, auto-updates, health checks, and diagnosing common problems.

Upgrading

Manual upgrade

Point GLASSY_TAG at the new release and pull

docker compose pull

First set GLASSY_TAG in .env to the released version you want (see the releases page) — never latest, which is the hosted build and omits self-host features. docker compose pull then fetches that version from GHCR.

Recreate the container

docker compose up -d

The new image starts. Database migrations apply automatically on startup — no manual intervention needed.

The entire process takes under a minute. Your data is safe — it lives in the glassy-data volume, which persists across container recreations.

Pinning a version before upgrading

If you want to stay on a specific released version instead of following every release:

GLASSY_TAG=v2.58.0

Then docker compose pull && docker compose up -d. To move forward later, change GLASSY_TAG to the newest released version (see the releases page) and re-run. Never set GLASSY_TAG=latest — the floating :latest tag is the hosted build and omits self-host features.

Automatic updates with Watchtower

The bundled Watchtower overlay exists for hands-off updates, but it only recreates containers whose image tag actually moves — and a pinned released version (the recommended setup) never moves, so in practice upgrading stays a manual re-pin:

docker compose -f docker-compose.yml -f docker-compose.watchtower.yml up -d

Watchtower polls daily and recreates the container when its image tag moves; with a pinned GLASSY_TAG it stays quiet. Migrations run automatically whenever a new container starts.

Health checks

Readiness endpoint

curl http://localhost:3000/ready

Returns JSON when healthy:

{"status":"ready", ...}

If you get HTML instead of JSON, the container is still starting up — wait a few seconds and retry.

Container logs

docker compose logs glassy

Look for Server listening on port 8080 (the internal port) to confirm startup. Error lines are prefixed with ❌.

In-app health

Open the Account HUD → Health card. It shows real-time server, database, and API status.

Debugging from inside the container

The runtime image includes curl for network diagnostics:

# Check if Ollama is reachable from the container
docker compose exec glassy curl -s http://host.docker.internal:11434/api/tags | head -c 200

# Check if Obsidian's Local REST API is reachable
docker compose exec glassy curl -s http://host.docker.internal:27124/ -H "Authorization: Bearer YOUR_KEY"

Common issues

Container won’t start

docker compose logs glassy

Common causes:

  • Missing JWT_SECRET or API_KEY_ENCRYPTION_KEY — both are required. Generate with openssl rand -hex 32.
  • Membership verification failed — look for ❌ lines. Ensure GLASSY_MEMBER_EMAIL matches an active membership and the cloud was reachable on first boot. Also ensure GLASSY_SELFHOST_TOKEN matches the token generated in Settings → Self-hosting on app.glassy.fyi.
  • Port conflict — if port 3000 is in use, set APP_PORT=3001.

Membership verification failed

The appliance must reach app.glassy.fyi on first boot to verify your membership. If your server has no internet on first boot:

  1. Connect to the internet temporarily
  2. Start the appliance and complete first-boot verification
  3. After that, you can go offline for up to 30 days

If your membership expired, renew it at clear.glassy.fyi and restart the container.

Obsidian not connecting

Browser extension bridge setup (Windows/WSL2 — the only working path):

  1. Install the Obsidian Local REST API plugin (v4.0+) in Obsidian. Copy the API key.
  2. Install the Glassy Capture extension in Chrome or Firefox.
  3. Sign in to the extension with your Glassy account.
  4. Extension popup → Settings → Obsidian Bridge → enter URL (http://127.0.0.1:27123) and API key → toggle on → Test Connection (tests the full bridge loop) → Save.
  5. Verify: curl -H "Authorization: Bearer <jwt>" http://localhost:3000/api/ext/obsidian-bridge/status → {"connected":true}.

If the extension says “Bridge connected” but the server says not connected:

  • Update to extension v2.20.1 — older versions held the SSE in the MV3 service worker (Chrome evicts after ~30s), and v2.12.0–v2.13.0 lacked localhost host permissions for the self-host Glassy server URL. v2.14.0+ uses the offscreen document (persistent, never evicted) and broadens optional_host_permissions to cover any localhost port. v2.15.0 adds a chrome.alarms heartbeat (survives SW eviction) that recreates the offscreen doc after 2+ missed heartbeats. v2.16.0 adds the MCP Settings UI for fetching your MCP key and generating client config snippets. v2.17.1 is the save & sync reliability release (capture-rule pre-population, 5xx-resilient sessions, offline-queue pause semantics, popup offline queueing, wildcard glassy.fyi host permissions). v2.18.0 adds bridge transport v2 — raw markdown bodies + request headers (If-Match, Content-Type, Accept) traverse the bridge and upstream ETag is relayed back, so vault writes (tap-to-toggle checkboxes, add-under-heading, daily-note append, push-to-vault) succeed over the bridge on containerized self-host where only the extension can reach Obsidian; older companions saw reads work but writes 502. v2.18.1 adds bridge reconnect hardening — the offscreen SSE reconnect delay drops 5000ms → 1000ms, so a dropped bridge recovers within a second (pairs with the server heartbeat fix in dash beta.28).
  • Update the server image to v2.35.0-beta.11+ if the bridge cycles every ~60 seconds. This is a server-side race condition (a stale SSE close handler deleting a newer connection in the registry), not a client issue. The fix tags each connection with a unique connectionId and only removes the connection if the IDs match.
  • Verify CLUSTER_WORKERS=1 in the container: docker exec glassy env | grep CLUSTER.
  • Toggle the bridge off and on in the extension popup to force a fresh SSE connection.

If server logs show 401 Invalid or expired SSE ticket:

  • Update the server image to v2.35.0-beta.9+. This is a server-side bug fixed in beta.9: two overlapping routers ran auth twice on each bridge request, consuming the one-time SSE ticket on the first run. The extension silently fell back to the less-secure ?token=<JWT> URL form (so the bridge still worked), but the JWT was leaking into server/proxy logs. beta.9 reorders the router mounts and adds a regression test. No extension or Obsidian configuration change is required.

For native Linux/macOS (direct path):

  • Verify the Local REST API plugin is enabled in Obsidian (v4.0+)
  • Check the plugin is bound to 127.0.0.1 (default) or 0.0.0.0 for split-machine setups
  • Check OBSIDIAN_NETWORK_ALLOWLIST if Obsidian is on a different machine

AI not working (NO_AI_PROVIDER)

This means no cloud AI provider is configured. Either:

  1. Install and run Ollama (see Ollama: Local Models)
  2. Add your own API key via BYOK (see BYOK)

Browser WebGPU AI still works for embeddings and small tasks regardless.

Slow performance

  • Check CLUSTER_WORKERS — set to 1 on a small box, raise on multi-core
  • If running Ollama, ensure you have enough RAM (4 GB+ recommended)
  • Check disk space — a full disk causes SQLite errors

Getting help

For issues not covered here:

You’ve completed the Self-Hosting track

Your self-hosted Glassy appliance is deployed, understood, backed up, and maintained. Explore the other tracks: