SubLaneSubLane

Deployment and upgrades

SubLane guide: deployment and upgrades.

For your first local setup, follow First request. Use this page when choosing a deployment method, configuring a remote instance or upgrading.

SubLane runs as one process with an embedded frontend and SQLite. Production does not require Node.js, Redis or a separate proxy service. Run one instance against each data directory.

Published Docker images

Tagged releases publish ghcr.io/murongg/sublane for linux/amd64 and linux/arm64. Docker selects the host architecture. Images are public and can be pulled without signing in to GHCR. Deployment requires Docker with Compose; no source checkout, Go, Node.js or local image build is needed.

The manual Compose examples below pin the published 0.1.0-rc.1 prerelease. Set SUBLANE_IMAGE explicitly for manual deployments: the Compose default is latest, which is only published with a stable release.

TagMeaning
0.1.0Example of a fixed stable version; use an actually published version
0.1.0-rc.1Published prerelease; does not update latest
latestNewest successfully promoted stable version
0.1.0-amd64 / 0.1.0-arm64Per-architecture release images

Use a fixed version or digest for repeatable deployments. A new image appearing in GHCR does not update a running container automatically.

Install script

Run the installer from a terminal to choose Docker or a published Linux binary, a normal instance or a read-only demo, then no proxy, Caddy or Nginx:

Use Up/Down or a number key to highlight an option, then Enter to confirm. The first option is selected by default. Basic terminals use numbered text prompts. The domain remains a text input, and the installer shows the generated configuration for confirmation before deployment. Supplied flags skip their corresponding choices; --non-interactive disables all prompts.

Install script · 1
curl -fsSL https://raw.githubusercontent.com/murongg/SubLane/main/scripts/install.sh | bash

Without --version, the script selects GitHub's latest stable release. Only when no stable release exists does it select the most recently published prerelease, excluding drafts. The selected concrete version is saved in the generated configuration, so restarts do not silently change versions. GitHub API errors stop installation instead of triggering a channel fallback.

The script verifies the selected release artifact against SHA256SUMS before installing it in a new ./sublane directory. Bash, curl and either sha256sum or shasum are required; automatic version selection additionally requires jq. Docker mode requires Docker Compose and waits for the container to become healthy. Binary mode supports Linux amd64/arm64, requires a working systemd user manager, enables a user service and waits for /readyz. It does not install Docker, Caddy or Nginx, and does not build from source.

To choose a published version, a new directory and a host port without prompts:

Install script · 2
curl -fsSL https://raw.githubusercontent.com/murongg/SubLane/main/scripts/install.sh | bash -s -- \
  --version 0.1.0-rc.1 --dir ./team-gateway --port 8088 --runtime docker --proxy none --non-interactive

Version tags may include the leading v. The backend port stays bound to 127.0.0.1. When no terminal is available, a normal instance using Docker with no proxy remains the default; pass --runtime, --proxy, or --demo to select other modes. For a remote server, use an SSH tunnel for initial setup or configure the HTTPS reverse proxy.

The installer uses ss (from iproute2) or lsof to check the selected backend port before downloading and again before creating the deployment. If a loopback or wildcard listener already uses that port, it stops and asks you to choose an unused port with --port, for example --port 8088. Install either utility if neither is available. An existing proxy on ports 80 or 443 does not block an installation using backend port 8080. The checks cannot reserve the port; startup health checks still catch conflicts that appear afterward.

For example, choose the published binary and generate a Caddyfile for a public domain:

curl -fsSL https://raw.githubusercontent.com/murongg/SubLane/main/scripts/install.sh | bash -s -- \
  --runtime binary --proxy caddy --domain sublane.example.com

--domain is optional with Caddy or Nginx. Without it, the generated proxy listens on 127.0.0.1:80 over HTTP and SUBLANE_PUBLIC_URL stays unset. With a domain, the installer sets SUBLANE_PUBLIC_URL=https://<domain> and generates an HTTPS proxy configuration. It shows the configuration before installing, but does not copy it into or reload the proxy service. Review the generated Caddyfile or nginx.conf and apply it yourself; the Nginx template assumes certificate files under /etc/letsencrypt/live/<domain>/.

For Docker, the installer writes the selected image, port and a unique Compose project name to .env. Keep it with docker.compose.yaml; it identifies the deployment's data volume. For binary mode, it generates sublane.env, start.sh and a linked systemd user unit; data stays in a private data/ directory. A systemd user service may stop when the user logs out unless lingering is enabled; check loginctl show-user "$USER" -p Linger and arrange lingering if you need unattended operation. New installations refuse existing directories and symlinks. An existing script-managed installation can be updated with the same script, as described below. If startup fails, configuration and any created data are retained for inspection.

Update a script-managed installation

First export and verify a database backup, and read the target release notes. Run the script from the parent of the installation directory. In a terminal, an existing target offers Update existing instance and asks for confirmation before deployment. To select updating explicitly:

Update an existing installation
curl -fsSL https://raw.githubusercontent.com/murongg/SubLane/main/scripts/install.sh | bash -s -- \
  --update --dir ./sublane

Without --version, updates use the same stable/prerelease selection as installation. Add --version <published-version> to pin a release, --wait-timeout 120 to change the readiness wait, or --non-interactive to skip confirmation. The script detects the existing runtime; installation-only flags such as --runtime, --port, --proxy and --demo are rejected during updates.

Docker updates preserve the Compose file and project name, change only SUBLANE_IMAGE in .env, pull the selected image, and recreate the service with a health wait. Binary updates verify the archive, replace the executable and packaged documentation/licensing files, restart the existing systemd user unit, and wait for /readyz. Environment files, launcher, user unit, proxy configuration and data paths are preserved. The service's existing port is expected to be occupied, so updates do not run new-installation port checks.

Download, checksum, configuration and image-pull failures leave the deployed files unchanged. Before replacement, the script saves previous program/configuration files in a private .update-backup.* directory inside the installation and prints its path. These files are not a database backup. Failed startup retains them and current deployment data for inspection, reports failure, and does not automatically run an older version against a potentially migrated database. Follow recovery and rollback when data restoration is needed. A concurrent update is refused; if the process was forcibly killed, remove .update-lock only after confirming no update remains active.

This update path supports installations created by the script. Manual deployments and deployments that require additional Compose override files should use the manual upgrade procedure, including all overrides on subsequent starts.

To review the script before running it, download it with curl -fsSL https://raw.githubusercontent.com/murongg/SubLane/main/scripts/install.sh -o install.sh, then run bash install.sh --help or bash install.sh after reviewing it.

Manual Compose deployment

Create a deployment directory and download docker.compose.yaml from the selected GitHub Release:

Manual Compose deployment · 3
mkdir sublane && cd sublane
curl -fsSL https://github.com/murongg/SubLane/releases/download/v0.1.0-rc.1/docker.compose.yaml -o docker.compose.yaml

In that directory, create a Compose .env file to keep the selected image version across restarts and upgrades:

Manual Compose deployment · 4
SUBLANE_IMAGE=ghcr.io/murongg/sublane:0.1.0-rc.1
SUBLANE_BIND_ADDRESS=127.0.0.1
SUBLANE_PORT=8080
SUBLANE_LOG_LEVEL=info
# Add this when using an HTTPS reverse proxy:
# SUBLANE_PUBLIC_URL=https://sublane.example.com
# In Docker, first identify the proxy source IP as seen inside the container:
# SUBLANE_TRUSTED_PROXIES=<proxy-source-ip>/32

Compose reads .env for interpolation; the standalone Go application does not load .env itself. An immutable digest can be used as the entire SUBLANE_IMAGE value instead of a tag.

Manual Compose deployment · 5
docker compose -f docker.compose.yaml pull
docker compose -f docker.compose.yaml up -d
docker compose -f docker.compose.yaml ps
docker compose -f docker.compose.yaml logs --tail=100 sublane

Open http://127.0.0.1:8080 and complete administrator setup before allowing member access. For a remote host, use an SSH tunnel for initial setup. The default Compose mapping stays on loopback; publishing 0.0.0.0 is an explicit operator choice.

The image runs as the existing Alpine sublane service account, includes CA certificates and license notices, and checks /readyz. Compose uses a read-only root filesystem, a writable 64 MiB /tmp tmpfs for SDK scratch files, dropped Linux capabilities, bounded container logs and a 30-second stop grace period. The container listens on port 8080; customize the host port through SUBLANE_PORT. If overriding the internal listening port yourself, also change the health check and port mapping.

Persistent data

The named sublane-data volume holds /data/sublane.db, its WAL files and /data/credentials.key. Keep the encryption key with the database. The existing Compose service/volume keys are preserved, so upgrades from earlier source builds keep using the same volume when run from the same Compose project.

Keep the Compose project name/directory consistent. Changing it creates a different named volume and can make an existing instance appear uninitialized. Do not run docker compose -f docker.compose.yaml down -v when upgrading or recovering: that removes the data volume. Bind mounts are supported, but their host permissions must let the image's non-root account write the mounted directory. Prefer the default named volume unless host path ownership is intentional.

Build from source

To build locally instead, clone the repository and use the source-build override:

Build from source · 6
git clone https://github.com/murongg/SubLane.git
cd SubLane
docker compose -f docker.compose.yaml -f docker.compose.build.yaml up --build -d

Use both files for subsequent commands against that deployment. To set build metadata:

Build from source · 7
VERSION=0.1.0-dev REVISION=$(git rev-parse HEAD) \
  docker compose -f docker.compose.yaml -f docker.compose.build.yaml build

The Dockerfile compiles Go for TARGETOS/TARGETARCH from native build stages, so building ARM64 on x86-64 does not emulate the compiler or frontend build. A multi-platform image can be built with a configured Buildx builder:

Build from source · 8
docker buildx build --platform linux/amd64,linux/arm64 \
  --build-arg VERSION=0.1.0-dev --output type=oci,dest=dist/sublane.oci.tar .

Create dist first. A regular local build/load normally targets one platform. See Docker's cross-compilation guidance.

Standalone Linux binary

Download the archive matching your machine and SHA256SUMS from the same release:

  • sublane_VERSION_linux_amd64.tar.gz: x86-64.
  • sublane_VERSION_linux_arm64.tar.gz: AArch64.

On Linux, verify the downloaded files, extract the archive into a new versioned directory, and run:

Standalone Linux binary · 9
sha256sum --check --ignore-missing SHA256SUMS
./sublane --version
SUBLANE_ADDR=127.0.0.1:8080 SUBLANE_DATA_DIR=/srv/sublane/data ./sublane

The archive contains the executable, AGPL and third-party license texts, the Compose file, an environment example and deployment/backup documentation. Keep the data directory outside the versioned executable directory, make it private and writable by the service user, and use a process supervisor for persistent operation. The guided installer can perform this setup with a systemd user service. Do not run the gateway as root.

Demo mode

The installer offers normal instance or read-only demo after the runtime choice. Normal mode remains the default. To select demo mode directly:

Install a read-only demo
curl -fsSL https://raw.githubusercontent.com/murongg/SubLane/main/scripts/install.sh | bash -s -- --demo

--demo works with both --runtime docker and --runtime binary. Add --non-interactive to use defaults for the remaining choices. The installer writes SUBLANE_DEMO=true to .env for Compose or sublane.env for the binary service, and displays the public demo login after installation. An inherited SUBLANE_DEMO does not override the installer's selected mode.

Demo support must be present in the selected release's verified configuration. The installer rejects older releases before pulling/starting containers or enabling a service; use --version to select a release that includes demo support. It does not fall back to a normal instance.

In a build containing demo support, start a read-only demonstration with:

Start a disposable demo
SUBLANE_DEMO=true ./sublane

Open the usual server address and choose Explore demo, or sign in with username demo and password sublane-demo. The application shows clearly labeled sample subscription accounts, pools, members, API key metadata, request history, and 30 days of usage. Navigation, filters, language, and theme preferences work normally. Forms can be inspected, but writes, credential disclosure, OAuth, backups, and all gateway calls (including WebSocket) are blocked; login and logout remain available.

Each start creates a new private temporary database and encryption key. Demo mode does not open SUBLANE_DATA_DIR, pricing caches, or real credentials, and does not contact providers or release/pricing services. Graceful shutdown removes the temporary files; an abrupt kill can leave a sublane-demo-* directory in the operating system's temporary directory. Restarting recreates the examples with recent dates. Stop the demo and unset SUBLANE_DEMO to resume a normal installation.

For development, use SUBLANE_DEMO=true make dev. With this version's Compose file, set SUBLANE_DEMO=true in .env and recreate the container. Existing published versions without demo support must be upgraded or rebuilt first. The ordinary listen-address, external-origin, and trusted-proxy settings still apply.

Runtime configuration

The standalone service reads these process environment variables; .env.example lists examples. It does not load a .env file automatically. Compose reads .env for its own interpolation and passes the configured environment into the container.

VariableStandalone defaultPurpose
SUBLANE_ADDR127.0.0.1:8080HTTP listening address
SUBLANE_DATA_DIR./dataSQLite database and encryption-key directory
SUBLANE_DEMOfalsetrue starts an isolated, disposable, read-only demo; accepts only true or false
SUBLANE_LOG_LEVELinfodebug, info, warn or error
SUBLANE_MAX_REQUEST_BODY_MB128Model request and reconstructed WebSocket context limit in MiB; positive integer
SUBLANE_PUBLIC_URLUnsetExact external origin; HTTPS enables secure session cookies
SUBLANE_TRUSTED_PROXIESUnsetComma-separated CIDRs of reverse proxies trusted to supply X-Forwarded-For for authentication limits

The Docker image overrides the listen address to 0.0.0.0:8080 and data directory to /data. SUBLANE_IMAGE, SUBLANE_BIND_ADDRESS and SUBLANE_PORT configure Compose only. SQLite initializes the consolidated schema on a fresh database and uses a single database connection.

SUBLANE_MAX_REQUEST_BODY_MB applies to gateway HTTP requests, WebSocket messages and reconstructed conversation context. Responses and stream events retain their separate 8 MiB limits. Restart the standalone service or recreate the container after changing the request limit, and align any reverse proxy body limits with the new value.

If you override SUBLANE_PRICING_URL, also set SUBLANE_PRICING_HASH_URL to the matching SHA-256 file to keep document integrity checks enabled. A custom price URL without a hash URL is fetched and parsed but is not hash-verified.

HTTPS reverse proxy

Set SUBLANE_PUBLIC_URL to the exact external origin, such as https://sublane.example.com, and recreate the service so the environment changes take effect. This is required behind a reverse proxy because the fallback derives the origin from the request's Host header. The configured origin is used for browser request checks and secure session cookies. Forward the original Host header and preserve streaming and WebSocket upgrades.

Set SUBLANE_TRUSTED_PROXIES to the CIDR addresses of your own proxy hops. For a standalone SubLane binary reached over IPv4 loopback, use 127.0.0.1/32 (add ::1/128 for IPv6 loopback). A Docker container may see the host proxy as its bridge address rather than loopback; the installer leaves this value empty in Docker mode unless you pass --trusted-proxies with the address SubLane actually sees. For a proxy container, use its fixed address or a dedicated, tightly scoped private subnet. SubLane ignores X-Forwarded-For unless the direct peer matches this list, then uses the rightmost untrusted IP in the chain for login limits. Keep direct access to SubLane restricted to the proxy when this is configured. Without it, all users behind one proxy share the proxy's login limit.

For a Caddy proxy running on the same host:

Caddyfile
sublane.example.com {
    reverse_proxy 127.0.0.1:8080
}

Caddy handles TLS and WebSocket upgrades and flushes SSE responses automatically. Keep the default flush behavior so client disconnects can cancel upstream work; negative flush_interval changes that cancellation behavior. See the Caddy reverse proxy reference. If the proxy is another container, use the Compose service name sublane:8080 on a shared network; 127.0.0.1 inside the proxy container refers to that container itself.

For an existing Nginx installation, put the map in the http context and use these locations inside its HTTPS server block:

HTTPS reverse proxy · 11
map $http_upgrade $sublane_connection {
    default upgrade;
    ''      close;
}

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $sublane_connection;
    proxy_buffering off;
    proxy_read_timeout 650s;
    proxy_send_timeout 650s;
    client_max_body_size 128m;
}

# Web backup uploads/downloads can be larger and last up to 15 minutes.
location /api/settings/backup/ {
    proxy_pass http://127.0.0.1:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_request_buffering off;
    proxy_buffering off;
    proxy_read_timeout 950s;
    proxy_send_timeout 950s;
    client_max_body_size 256m;
}

Configure the HTTPS certificate in the parent Nginx server. The Nginx WebSocket guide explains the explicit upgrade headers. Limits must also permit the request/backup sizes at any other proxy layer. The gateway's own authorization and body limits remain in force.

Upgrade

  1. Review the release notes and record the current image version/digest.

  2. Export a consistent backup and copy it off the data volume:

    Upgrade · 12
    docker compose -f docker.compose.yaml exec sublane sh -c 'mkdir -p /data/backups'
    docker compose -f docker.compose.yaml exec sublane sublane backup --output /data/backups/pre-upgrade.sublane-backup.tar.gz
    docker compose -f docker.compose.yaml exec sublane sublane backup verify --input /data/backups/pre-upgrade.sublane-backup.tar.gz
    docker compose -f docker.compose.yaml cp sublane:/data/backups/pre-upgrade.sublane-backup.tar.gz ./pre-upgrade.sublane-backup.tar.gz
    chmod 600 ./pre-upgrade.sublane-backup.tar.gz

    Use a new filename for each upgrade; existing backups are never overwritten. The archive includes the encryption key and belongs in private storage.

  3. Change SUBLANE_IMAGE in .env to the selected version, then pull and recreate:

    Upgrade · 13
    docker compose -f docker.compose.yaml pull
    docker compose -f docker.compose.yaml up -d
    docker compose -f docker.compose.yaml ps
    docker compose -f docker.compose.yaml logs --tail=100 sublane
  4. Confirm health, sign-in, expected accounts/pools/keys, and one explicitly authorized client call. A fresh database receives the consolidated SQLite schema at startup. Image health verifies process/database readiness, not real provider authorization.

Recovery and rollback

Changing an image tag alone is not a database rollback. If a release migrated the schema, restore the pre-upgrade archive into a new directory with the intended older binary/image, then stop the current service and switch to that restored directory. Retain the original volume until verification is complete. Never let two processes share the same SQLite directory.

The backup guide covers CLI and web restore preparation and the Compose override for /data/restore-ready. Continue using the restore override on later starts while that directory is active. A backup from a newer schema cannot be opened by an older release that does not recognize it.

Container verification

With a Docker engine running:

Container verification · 12
docker compose -f docker.compose.yaml config --quiet
docker compose -f docker.compose.yaml -f docker.compose.build.yaml config --quiet
docker buildx build --load --build-arg VERSION=0.0.0-test -t sublane:smoke .
bash scripts/container-smoke.sh sublane:smoke 0.0.0-test

The smoke script creates its own disposable named volume and container, with networking disabled. It checks health, version, non-root execution, read-only runtime paths, first-run setup with synthetic credentials, key permissions, backup/verify/restore and persistence after restart. It removes only those temporary resources. It does not contact upstream providers or use existing instance data. CI runs the same checks on native amd64 and arm64 runners.