Skip to main content

Installation guide

Written by Aron Day

This is the complete, step-by-step guide to installing 3B on one Linux server using the two scripts that do all the work:

  • bootstrap.sh — the one command you run. It signs in to the Tines registry, downloads the right release bundle, and hands off to setup.sh.

  • setup.sh — the installer inside the bundle. It checks the server, asks you a handful of configuration questions, installs a lightweight Kubernetes (k3s), generates secrets, loads the software, and starts 3B.

You never download or run setup.sh yourself in this guide; bootstrap.sh runs it for you.

Installing onto your own existing Kubernetes cluster with the Helm chart directly is a separate document — see the Helm installation guide. This guide covers the self-contained, single-server install only.

Before you start, work through the prerequisites. It has a checklist of everything the server needs (sizing, OS, kernel, tools, credentials, DNS, ports, and TLS).
A standard install usually takes about 10–20 minutes, most of it spent downloading the container images.

What you are installing

This installer puts all of 3B onto one Linux server. To run and connect the pieces, it installs k3s — a lightweight, certified Kubernetes that runs as a single node — and deploys 3B onto it.

This is an evaluation install, not a production deployment. It is designed for proofs of value (POVs) and proofs of concept (POCs). There is no high availability and no redundancy: if the server goes down, 3B goes down with it. For production, use a Kubernetes cluster and install 3B with the Helm chart instead.

You do not need to know Kubernetes to follow this guide. The installer sets it up and manages it for you; a few optional commands in operations use it only to check health or read logs.

How the two scripts fit together

The install happens in two stages, but you only ever type one command:

  1. bootstrap.sh — runs on any server with registry access. It checks the server, asks install or air-gapped and which version, signs in to the OCI registry, downloads the bundle, and extracts it into /opt/3b. It performs no installation work of its own.

  2. setup.sh — ships inside the bundle, together with everything it needs: the Helm chart, the bundled k3s and Helm binaries, the configuration template, and (for air-gapped installs) the container images. It re-checks the server, asks for your URLs and TLS choice, installs k3s and Helm, generates all the secret keys, loads or pulls the images, and deploys and starts every service.

Everything is installed into the directory /opt/3b. That is where the configuration file, the Helm chart, the bundled binaries, and the installer live after the bundle is extracted.

Standard install (server with internet access)

This is the normal path. Run it on the server itself, as root.

Step 1 — Run the command

With curl:

sudo bash -c "$(curl --proto '=https' --tlsv1.2 -fsSL https://sh.3b.dev)"

Or with wget:

sudo bash -c "$(wget -qO- https://sh.3b.dev)"

This downloads bootstrap.sh and runs it as root. Nothing is saved permanently outside /opt/3b.

The command downloads the whole script before running any of it, so a dropped connection can’t leave you halfway through an install. To read the script first, open https://sh.3b.dev in a browser or download it with curl --proto '=https' --tlsv1.2 -fsSL -O https://sh.3b.dev.

Step 2 — Answer the bootstrap prompts

The script prints a banner and then walks you through these questions in order. Defaults are shown in brackets; press Enter to accept them.

  1. Install or airgapped? [install] — press Enter (or type install) for a normal install on this connected server. (Type airgapped only if you are preparing a bundle to move to an offline server — see the air-gapped installation.)

  2. Release version (e.g. v1.0.10) — type the version Tines gave you, for example v2.0.0.

  3. Tines tenant name (oci.tines.com username) — your registry username.

  4. Tines Docker Registry API key — your registry password. It is hidden as you type.

The script then signs in to oci.tines.com, downloads the release bundle into /opt/3b, extracts it, and automatically launches setup.sh:

3B self-hosted installer

==> Checking the host
    running as root on amd64 Linux

==> Configuration
    install     set up 3B on this server now
    airgapped   download the bundle to move to an offline server
Install or airgapped? [install]:
Release version (e.g. v1.0.10): v2.0.0

==> Registry access
    Sign in to oci.tines.com with your Tines tenant name and API key.
Tines tenant name (oci.tines.com username): acme
Tines Docker Registry API key:
    Logging in to oci.tines.com… ok

==> Downloading the release
    Extracting bundle… ok

==> Running setup
=== 3B Self-Hosted Setup ===

Step 3 — Answer the configuration prompts

Now setup.sh takes over and asks for your configuration. Again, defaults are in brackets.

  1. Release version — confirm the version (often pre-filled from the bundle, in which case this prompt is skipped).

  2. Stack name (stable unique identifier) — a short, stable identifier for this installation (for example tines-3b-poc). Choose it once and never change it after the first start without migrating data, because some stored data is scoped to it.

  3. Protocol for the app URLs (http/https) [https] — press Enter for HTTPS.

  4. Root domain for the app URLs (e.g. example.com) — type your root domain. From this, the installer proposes a default for each of the next prompts.

  5. EXTERNAL_UI_URL, EXTERNAL_API_URL, ZERO_SYNC_URL — accept the proposed defaults or override them. By default all three share one hostname (https://3b.example.com); the UI, API, and real-time sync are routed by path behind that origin.

  6. EXTERNAL_PUBLIC_URL — the address 3B shares externally (webhooks and public links), on its own hostname (default https://3b-public.example.com).

  7. COOKIE_DOMAIN (no scheme or port) — accept the proposed default (your root domain) or override it.

  8. Signed 3B license token (3bl_v1_…) from Tines — paste your license token as a single line, with no extra spaces or line breaks.

  9. TLS choice (only if you chose https) — pick option 1, 2, or 3; see TLS termination.

Step 4 — Review and confirm

Before changing anything on the server, the installer prints the full configuration it is about to use, with all secrets redacted, and asks you to confirm:

About to install with this configuration (from /opt/3b/.env, secrets redacted):
------------------------------------------------------------------
VERSION='v2.0.0'
STACK_NAME='acme-poc'
EXTERNAL_UI_URL='https://3b.example.com'
EXTERNAL_API_URL='https://3b.example.com'
EXTERNAL_PUBLIC_URL='https://3b-public.example.com'
ZERO_SYNC_URL='https://3b.example.com'
COOKIE_DOMAIN='example.com'
USE_SECURE_COOKIES='true'
REGISTRY='oci.tines.com'
REGISTRY_USERNAME='acme'
REGISTRY_PASSWORD=<redacted>
LICENSE=<redacted>
NGINX_TLS_MODE='self-signed'
NGINX_TLS_CERT_PATH='/etc/ssl/3b/fullchain.pem'
NGINX_TLS_KEY_PATH=<redacted>
POSTGRES_PASSWORD=<redacted>
# … additional generated secrets and keys, all redacted …
------------------------------------------------------------------
Proceed? [Y/n]:
  • Press Enter (or y) to continue.

  • Press n to stop. Nothing has been changed yet. You can edit /opt/3b/.env and re-run with sudo bash /opt/3b/setup.sh.

Step 5 — Wait for it to finish

After you confirm, the installer:

  • generates all the secret keys and encryption keys it needs,

  • installs and starts k3s (the single-node Kubernetes) and Helm,

  • loads or pulls the container images,

  • creates the 3b namespace and its secrets, then deploys the Helm chart,

  • waits for the database migration to finish and for every service to roll out and become healthy.

This typically takes several minutes, mostly downloading images. The tail of a successful run looks like this:

Installing k3s…
Installing Helm…
Release "3b" does not exist. Installing it now.
job.batch/migration condition met
deployment "api" successfully rolled out
deployment "orchestrator" successfully rolled out
deployment "public" successfully rolled out
deployment "worker" successfully rolled out
deployment "nginx" successfully rolled out
deployment "zero-sync" successfully rolled out
statefulset rolling update complete: postgres
statefulset rolling update complete: blobstore

=== 3B is up ===
UI: https://3b.example.com

Continue to verifying the install to confirm everything is healthy.

Unattended / scripted install

For automation (cloud-init, configuration management, CI), you can pre-answer every prompt with environment variables so nothing is interactive. bootstrap.sh reads these directly:

Variable

Meaning

CONNECTIVITY

install or airgapped

VERSION

release version, e.g. v2.0.0

REGISTRY_USERNAME

Tines tenant name

REGISTRY_PASSWORD

Tines OCI registry API key

Because setup.sh runs as a child of bootstrap.sh, any configuration variables you export are also picked up by it. For a fully unattended standard install, export the registry answers and the configuration that setup.sh would otherwise prompt for:

sudo \
  CONNECTIVITY=install \
  VERSION=v2.0.0 \
  REGISTRY_USERNAME=your-tenant \
  REGISTRY_PASSWORD=your-api-key \
  STACK_NAME=acme-poc \
  EXTERNAL_UI_URL=https://3b.example.com \
  EXTERNAL_API_URL=https://3b.example.com \
  EXTERNAL_PUBLIC_URL=https://3b-public.example.com \
  ZERO_SYNC_URL=https://3b.example.com \
  COOKIE_DOMAIN=example.com \
  USE_SECURE_COOKIES=true \
  LICENSE=3bl_v1_… \
  bash -c "$(curl --proto '=https' --tlsv1.2 -fsSL https://sh.3b.dev)"

Notes:

  • When there is no terminal, the installer skips the “Proceed?” confirmation and uses the values as given. Any required value that is missing causes the install to stop with a clear error, so set them all.

  • To pre-supply a TLS certificate non-interactively, also export NGINX_TLS_CERT_PATH and NGINX_TLS_KEY_PATH pointing to files already on the server (and use https:// URLs).

  • Alternatively, stage a complete /opt/3b/.env before running; the installer prompts only for what is missing and reuses everything already set. See the configuration reference.

Verifying the install

The commands below use k3s kubectl, the Kubernetes command-line tool bundled with k3s. Run them as root.

Check the pods are running

sudo k3s kubectl -n 3b get pods

You should see output like this — the application pods in Running state, and the one-off migration job Completed:

NAME                           READY   STATUS      RESTARTS   AGE
api-7c9f8b6d4-2xk9p            1/1     Running     0          3m
orchestrator-5d8c7f9b6-ll4qz   1/1     Running     0          3m
public-6b7d9c8f5-r8m2t         1/1     Running     0          3m
worker-9f6c5b8d7-qp3vn         2/2     Running     0          3m
nginx-7d6f8c9b5-h4k7w          1/1     Running     0          3m
zero-sync-8c7b6d9f4-zt5xr      1/1     Running     0          3m
postgres-0                     1/1     Running     0          3m
blobstore-0                    1/1     Running     0          3m
migration-abcde                0/1     Completed   0          3m

(It is normal for the migration pod to be Completed rather than Running — it does its job once and exits. Pod name suffixes are random and will differ on your server.)

To watch them settle, add -w to the command, or check the overall rollout:

sudo k3s kubectl -n 3b get deployments,statefulsets,jobs

Check the endpoints respond

Browse to your UI URL (for example https://3b.example.com) from a machine that can resolve the hostname. You can also check from the server:

curl --proto '=https' --tlsv1.2 -sf -o /dev/null -w "%{http_code}\n" https://3b.example.com
200

A 200 means the UI is serving. (For an HTTP-only evaluation, use http:// and the appropriate hostname. With a self-signed certificate, add -k to skip certificate verification for this check.)

Where to look if something is off

  • Pod status and recent logs: sudo k3s kubectl -n 3b get pods and sudo k3s kubectl -n 3b logs deploy/<service> (for example deploy/api).

  • See troubleshooting for common messages and what they mean.

First sign-in

Open your UI URL in a browser — for example https://3b.example.com (the address the installer printed as UI: at the end).

If you used a self-signed certificate, your browser will warn that the connection is not trusted; accept the exception to proceed. From there, create your first account and start exploring. If you configured SMTP, invitations and other emails will be sent through it.

Next steps

Did this answer your question?