SubLaneSubLane

Deployment and upgrades

SubLane guide: deployment and upgrades.

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

With Docker, Compose, curl and jq installed:

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 image version is saved in .env, so restarts do not silently change versions. GitHub API errors stop installation instead of triggering a channel fallback.

The script creates ./sublane, verifies the release Compose file against SHA256SUMS, pulls the published image and waits for the container to become healthy. Bash, curl and either sha256sum or shasum are required; automatic version selection additionally requires jq. It does not install Docker or build from source.

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

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

Version tags may include the leading v. The port stays bound to 127.0.0.1. For a remote server, use an SSH tunnel for initial setup or configure the HTTPS reverse proxy.

The installer writes the selected image, port and a unique Compose project name to .env. Keep this file with docker.compose.yaml; it identifies the deployment's data volume. Existing target directories, including symlinks, are refused without changes. If startup fails, the configuration and any container data are retained for inspection. Use the upgrade procedure for existing installations.

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

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 team 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. Do not run the gateway as root.

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_LOG_LEVELinfodebug, info, warn or error
SUBLANE_PUBLIC_URLUnsetExact external origin; HTTPS enables secure session cookies

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 applies ordered migrations at startup and uses a single database connection.

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 origin is used for browser request checks and secure session cookies. Forward the original Host header and preserve streaming and WebSocket upgrades.

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 Upgrade $http_upgrade;
    proxy_set_header Connection $sublane_connection;
    proxy_buffering off;
    proxy_read_timeout 650s;
    proxy_send_timeout 650s;
    client_max_body_size 8m;
}

# 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_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/groups/keys, and one explicitly authorized client call. SQLite migrations run 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.