Upgrade Temps

Temps includes a self-upgrade command that downloads the latest release from GitHub, verifies its checksum, and replaces the binary atomically. Database migrations run automatically on the next restart — or, for production and large databases, apply them as an explicit step with temps migrate before restarting.


Check your current version

temps --version

Output:

{TEMPS_VERSION} (abc1234) built 2026-06-17 12:34:56 UTC

Check for updates

See if a newer version is available without installing it:

temps upgrade --check

Or use the health check command, which includes an update check along with other system diagnostics:

temps doctor

If a newer version exists, you will see:

WARN Update: v1.1.0 available - run 'temps upgrade' to update

Pre-upgrade checklist

Run temps doctor before every upgrade. It surfaces the one thing that most often goes wrong on a busy instance — pending database migrations on large tables:

temps doctor

Look at the Migrations line:

PASS Migrations: 107 applied, up to date          ← safe to upgrade
WARN Migrations: 107 applied, 3 PENDING — run `temps migrate` before restarting

If it reports PENDING, the new binary needs to apply schema changes. On a small database these run in seconds. On a large one — for example a proxy_logs request-log table with millions of rows — a migration that builds an index can take several minutes. If that happens during server startup it can exceed the boot migration window and leave the service in a restart loop.

The fix is simple and is the recommended flow below: apply migrations as an explicit step with temps migrate, then restart the server. Because you run it by hand with no time pressure, a slow migration just takes a while — it can never crash-loop the API.


Back up before upgrading

Back up before upgrading

  1. 1

    Open the dashboard and go to Settings > Backups.

  2. 2

    Click Run Backup to trigger a manual backup against your configured S3 backup storage.

  3. 3

    If you do not have S3 storage configured, instead copy the data directory and dump the database from the shell: cp -r ~/.temps ~/.temps-backup-$(date +%Y%m%d) and docker exec temps-timescaledb pg_dumpall -U postgres | gzip > temps-db-backup-$(date +%Y%m%d).sql.gz.

    Checkpoint: Confirm the backup includes ~/.temps/encryption_key -- without it you cannot decrypt stored service credentials.

Before any upgrade, create a backup of your data:

If you have S3 backup storage configured:

Trigger a manual backup from the dashboard (Settings > Backups > Run Backup) or via the API.

If you do not have S3 configured:

At minimum, back up the Temps data directory and database:

# Back up the data directory (contains encryption key, auth secret, GeoLite2 DB)
cp -r ~/.temps ~/.temps-backup-$(date +%Y%m%d)

# Back up the PostgreSQL database
docker exec temps-timescaledb pg_dumpall -U postgres | gzip > temps-db-backup-$(date +%Y%m%d).sql.gz

For production and any instance with a large database, apply migrations as an explicit step with the new binary before restarting the server. This is the safest sequence and the one we recommend for all multi-user installs:

# 1. Back up (see the section above)

# 2. Download + swap the new binary. `temps upgrade` replaces the binary in
#    place; it does NOT restart the service, so the running server keeps using
#    the old binary already loaded in memory until you restart it.
temps upgrade -y

# 3. Stop the server
sudo systemctl stop temps

# 4. Apply migrations explicitly with the new binary — no time limit, watch it complete
temps migrate --database-url="$TEMPS_DATABASE_URL"

# 5. Start the server (schema is already up to date, so boot is fast)
sudo systemctl start temps

# 6. Confirm
temps doctor

temps migrate connects, then prints the plan up front — the pending migrations it is about to apply — and applies them one at a time, printing a line per migration with its name and how long it took, followed by a summary. If a migration fails it stops there, marks it with , shows the error, and lists the ones it did not reach, so you know exactly where the run stopped and the database's state. After a clean run it also performs any continuous-aggregate backfill. Because it runs with the server stopped and no startup timeout, a slow index build on a large table simply runs to completion instead of risking a crash loop.

Running database migrations…

Pending migrations (3):
  - m20260601_000009_metrics_caggs_keep_labels
  - m20260601_000010_add_service_id_to_alarms
  - m20260603_000001_create_otel_trace_summaries

Applying…
  ✓ m20260601_000009_metrics_caggs_keep_labels (44ms)
  ✓ m20260601_000010_add_service_id_to_alarms (8ms)
  ✓ m20260603_000001_create_otel_trace_summaries (1.10s)

✓ 3 migration(s) applied in 1.15s. Safe to restart the server.

When the database is already current it prints ✓ Database already up to date — no migrations to apply. and exits, so it is safe to run on every deploy.


Run the upgrade

Run the upgrade

  1. 1

    These steps run on the server host (via SSH or a terminal on the machine), not in the web dashboard. Run temps upgrade (or sudo temps upgrade if the binary lives in a root-owned path like /usr/local/bin/temps); for production prefer the recommended flow that applies migrations explicitly first.

  2. 2

    The upgrader detects your platform, fetches the latest GitHub release, and exits early if you are already up to date.

  3. 3

    Review the upgrade plan it prints (current version, target version, download size) and confirm, or pass --yes / -y to skip the prompt.

    Checkpoint: It verifies the SHA-256 checksum before swapping, then replaces the binary atomically -- the running server keeps the old binary in memory until you restart.

  4. 4

    Restart the service to load the new binary: sudo systemctl restart temps.

The one-command path below is fine for simple installs. For production, prefer the recommended flow above.

Upgrade to the latest version

temps upgrade

If the binary is in a root-owned location (e.g. /usr/local/bin/temps):

sudo temps upgrade

The upgrade process:

  1. Detects your platform (Linux/macOS, x86_64/ARM64)
  2. Fetches the latest release from GitHub
  3. Compares versions — exits if already up to date
  4. Shows the upgrade plan (current version, target version, download size)
  5. Asks for confirmation
  6. Downloads the release tarball
  7. Verifies the SHA-256 checksum (if available)
  8. Replaces the binary atomically (write to temp file, then rename)

Options

# Upgrade to a specific version (ignores the channel)
temps upgrade --version v1.1.0

# Skip the confirmation prompt
temps upgrade --yes

# Track a release channel (stable is the default)
temps upgrade --channel beta

# Upgrade a binary at a different path
temps upgrade --path /opt/temps/bin/temps
FlagDefaultDescription
--channel <stable|beta>stableWhich release stream to track. See Release channels.
--version <vX.Y.Z>latest in channelPin a specific version. Bypasses channel filtering entirely.
--path <path>currently running binaryPath to the temps binary to replace.
--yes, -yoffSkip the confirmation prompt.
--checkoffPreview the planned upgrade without installing.

Other upgrade methods

temps upgrade is the recommended path because it verifies the checksum and swaps the binary atomically. These alternatives exist for installs that were set up with the deploy script or that prefer a one-shot command.

Upgrade script (deploy.sh installs)

If you installed Temps with deploy.sh, the upgrade script handles everything in one command — it backs up your encryption key, downloads the latest binary, and restarts the services:

curl -fsSL https://temps.sh/upgrade.sh | bash

To pin a specific version:

curl -fsSL https://temps.sh/upgrade.sh | bash -s vX.Y.Z

Re-run the installer

The installer downloads the latest binary for your platform and replaces the one at ~/.temps/bin/temps, preserving all configuration, data, and certificates. It accepts the same --channel flag as temps upgrade:

# Latest stable (default)
curl -fsSL https://temps.sh/install.sh | bash

# Latest beta
curl -fsSL https://temps.sh/install.sh | bash -s -- --channel beta

Then restart the service (see Restart Temps).

Manual download

Download a release tarball directly from GitHub, extract it, and replace the binary:

curl -LO https://github.com/gotempsh/temps/releases/latest/download/temps-linux-amd64.tar.gz
tar -xzf temps-linux-amd64.tar.gz
sudo mv temps /usr/local/bin/temps
sudo systemctl restart temps

To pin a version, swap latest/download for download/vX.Y.Z.


Release channels

temps upgrade tracks one of two release streams, selected with --channel:

ChannelSelectsNotes
stable (default)Non-prerelease GitHub releases onlyTags without a - suffix, e.g. v1.2.0.
betaBoth stable and prerelease releasesIncludes tags like v1.2.0-beta.4 or v1.2.0-rc.1.

Running temps upgrade with no flags always lands on stable.

Because GitHub returns releases newest-first and beta accepts both kinds, a beta host always upgrades to the freshest available version — including a newly shipped stable that supersedes the latest beta on the same line. The runtime decision is GitHub's prerelease flag on each release (draft releases are never selectable on either channel), not literal tag-string parsing.

Pinning a specific --version ignores the channel entirely and fetches that exact release directly.

The install.sh installer

The curl-pipe installer accepts the same --channel flag with matching semantics (default stable, accepts only stable or beta):

# Install the latest stable release (default)
curl -fsSL https://temps.sh/install.sh | bash

# Install the latest beta release
curl -fsSL https://temps.sh/install.sh | bash -s -- --channel beta

Stable resolves through GitHub's /releases/latest endpoint (which returns the newest non-prerelease); beta takes the newest tag from /releases?per_page=20. Passing an explicit version positional argument ignores the channel. One divergence from the Rust CLI: the bash installer does not filter draft releases — it trusts that the GitHub API only returns shipped releases, whereas the CLI explicitly skips drafts.


Restart Temps

The upgrade replaces the binary but does not restart the running process. You need to restart manually.

If running under systemd (typical server setup)

sudo systemctl restart temps

If you run the proxy as a separate process (split-process mode):

sudo systemctl restart temps-proxy

See Split-process upgrade (zero-downtime) for the recommended sequence when running in split-process mode — you can restart the console without touching the proxy, keeping traffic flowing the entire time.

If running in a terminal

Stop the current process (Ctrl+C) and start it again:

temps serve

Database migrations

There are two ways migrations get applied:

Automatic (on boot). When temps serve starts it connects to PostgreSQL, applies any pending migrations (10-minute timeout), backfills new TimescaleDB continuous aggregates, and then starts serving traffic. This is the default and is fine for simple, single-node installs. If migrations fail, the server will not start — check the logs for the specific migration error.

Manual (temps migrate). Apply pending migrations as an explicit step, with the server stopped and no timeout:

temps migrate --database-url="$TEMPS_DATABASE_URL"

This is the recommended path for production and large databases — see Recommended upgrade flow. Running it before you restart means the server boots against an already-migrated schema, so a slow index build on a big table can't trip the startup timeout and cause a crash loop.

Both paths are safe across versions and safe to re-run: only the migrations this binary defines that aren't already recorded get applied. Migration rows written by a different build (a newer version, or the Enterprise Edition) are left untouched and never cause a failure — so a mixed or partially-rolled-back history won't block startup.


Split-process upgrade (zero-downtime)

In the default single-process setup, temps serve runs both the Pingora reverse proxy and the console (API + dashboard) inside a single OS process. A restart means a brief interruption for in-flight requests.

Split-process mode separates the two roles into independent processes:

  • temps proxy — the Pingora reverse proxy only. It forwards incoming traffic to your deployed apps. It does not depend on the console being alive.
  • temps serve --split (or temps-console.service) — the console process: REST API, dashboard, background jobs, database migrations. It does not handle incoming app traffic directly.

Because the proxy and the console are independent, you can restart the console alone to load a new binary and apply migrations while the proxy continues forwarding traffic uninterrupted. Users and deployed apps see zero downtime.

Starting in split-process mode

Pass --split to temps serve to start only the console role. The proxy must be started separately:

# Start the proxy (keeps running across console restarts)
temps proxy

# Start the console in split mode
temps serve --split

Under systemd, two separate units manage the processes:

UnitRoleRestart policy
temps-proxy.servicePingora reverse proxy — forwards app trafficRarely restarted
temps-console.serviceConsole: API, dashboard, jobs, migrationsRestarted on every upgrade

The temps-console.service unit should declare After=temps-proxy.service so systemd starts the proxy first on boot, but there is no hard dependency — the console comes up fine even if the proxy is temporarily down, and vice versa.

Version-skew handling

When you restart the console with a new binary but leave the proxy on the old one (or the reverse), there is a brief window where proxy and console are running different versions. Temps handles this gracefully:

  • The proxy reads its routing table from PostgreSQL on startup and reloads it in-process on demand. It does not call the console's API at runtime for routing decisions, so a version mismatch does not break traffic forwarding.

  • On startup, the console emits a warning in its logs if it detects the proxy's reported version differs from its own:

    WARN version skew detected: console v0.1.1, proxy v0.1.0 — restart temps-proxy to clear
    
  • Routing continues normally during the skew window. The proxy version reported in temps doctor and the health endpoint reflects what the proxy process last registered.

  • The safe skew window is one minor version (e.g. console v0.1.1, proxy v0.1.0 is fine). Spanning two or more minor versions is not tested and may cause incompatibilities in the coordination protocol.

If you see the skew warning after a console upgrade, plan to restart the proxy in the next low-traffic window using the sequence below.

This sequence keeps the proxy forwarding traffic throughout the upgrade. The console is the only process that restarts, and it is down for only the seconds it takes to apply migrations and boot:

# 1. Back up (see the Back up before upgrading section)

# 2. Download and swap the new binary (does NOT restart anything)
temps upgrade -y

# 3. Stop the console only — the proxy keeps serving traffic
sudo systemctl stop temps-console

# 4. Apply migrations explicitly with the new binary (no startup timeout)
temps migrate --database-url="$TEMPS_DATABASE_URL"

# 5. Start the new console — schema is already current so boot is fast
sudo systemctl start temps-console

# 6. Confirm the console is healthy
temps doctor

# 7. (Optional) Restart the proxy to clear any version-skew warning
#    Do this in a low-traffic window — the proxy reload takes a few seconds
sudo systemctl restart temps-proxy

Step 7 is optional if you are within the safe one-minor-version skew window and can schedule the proxy restart later.


Verify the upgrade

After restarting:

# Check the version
temps --version

# Confirm the service is healthy
curl -sf http://localhost:8081/health

# Run diagnostics
temps doctor

The doctor command checks:

  • Data directory integrity
  • Encryption key presence
  • Database connectivity and version
  • TimescaleDB extension
  • Migrations — how many are applied, and whether any are still pending (it tells you to run temps migrate if so)
  • Docker daemon availability
  • External connectivity (GitHub API, Docker Hub, Let's Encrypt)
  • Git providers and DNS providers

If everything passes, your upgrade is complete. Visit the dashboard to confirm it loads correctly.


Upgrade the database

Apply database migrations explicitly

  1. 1

    These steps run on the server host (via SSH or a terminal on the machine), not in the web dashboard. Stop the server first so migrations run with no startup timeout: sudo systemctl stop temps.

  2. 2

    Apply pending schema migrations with the server binary: temps migrate --database-url="$TEMPS_DATABASE_URL". This is the server binary's schema migration command — not the @temps-sdk/cli platform-migration wizard. It prints the plan up front and applies each migration one at a time, printing a checkmark and timing per migration.

    Checkpoint: Watch for the summary line -- e.g. "3 migration(s) applied. Safe to restart the server." -- or "Database already up to date" if there was nothing to apply.

  3. 3

    Start the server again now that the schema is current, so boot is fast: sudo systemctl start temps.

  4. 4

    Confirm everything is healthy with temps doctor and check the Migrations line reads "applied, up to date".

TimescaleDB runs as a Docker container and is upgraded independently of Temps:

# Pull the latest image
docker pull timescale/timescaledb-ha:pg18

# Stop and remove the old container (data persists in the volume)
docker stop temps-timescaledb
docker rm temps-timescaledb

# Start with the new image
docker run -d \
  --name temps-timescaledb \
  --restart unless-stopped \
  -e POSTGRES_USER=temps \
  -e POSTGRES_PASSWORD="YOUR_DB_PASSWORD" \
  -e POSTGRES_DB=temps \
  -p 127.0.0.1:5432:5432 \
  -v temps-db-data:/home/postgres/pgdata/data \
  timescale/timescaledb-ha:pg18

# Restart Temps to reconnect
sudo systemctl restart temps

Your database password is stored in ~/.temps/.wizard-state/db_password if you used the deploy script.


Switch to Enterprise Edition

Switch to Enterprise Edition

  1. 1

    Make sure you are on linux-amd64 -- EE currently ships that platform only and the command fails early on anything else.

  2. 2

    Run temps upgrade --tier ee --license-path /path/to/license.jwt (add --check to preview, or -y to skip the confirmation prompt).

  3. 3

    The CLI validates the license JWT shape, resolves the EE version, fetches the checksum + tarball from the EE proxy authenticated with your license, and verifies the checksum before the atomic swap.

    Checkpoint: It copies the license to <data-dir>/data/license.jwt (mode 0600) and, on Linux, wires TEMPS_EE_LICENSE_PATH into the systemd unit if not already present.

  4. 4

    Restart the service to activate the EE binary: sudo systemctl restart temps (the EE binary refuses to start without a valid license).

temps upgrade --tier ee switches a running install from the open-source binary (the default) to the Enterprise Edition (EE) binary. The EE binary lives in a license-gated proxy, so you must supply a license JWT.

temps upgrade --tier ee --license-path /path/to/license.jwt

EE flags

FlagDefaultDescription
--tier <oss|ee>ossWhich edition to install. oss downloads from GitHub releases. CLI-only — no env-var fallback.
--license-path <path>Path to the EE license JWT. Required with --tier ee.
--ee-api <url>https://temps.shBase URL of the Temps Cloud EE proxy (--tier ee only). A trailing slash is trimmed.
--data-dir <path>$TEMPS_DATA_DIR, else ~/.tempsData dir whose data/license.jwt receives the license. Also reads the TEMPS_DATA_DIR env var.

If you pass --tier ee without --license-path, the command errors with:

--tier ee requires --license-path <path-to-license.jwt>. Download yours from https://temps.sh/dashboard/license

What the switch does

  1. Validates the license JWT shape before downloading anything. The CLI decodes (but does not verify the signature — the EE binary checks the signature at boot) and rejects: malformed JWTs that are not three dot-separated segments, any tier claim that is not premium or enterprise, and licenses whose exp claim is already in the past.
  2. Resolves the version — the pinned --version, or the latest from <ee-api>/api/ee/releases.
  3. Fetches the checksum and tarball from <ee-api>/api/ee/download/<version>/<asset>/sha256 and <ee-api>/api/ee/download/<version>/<asset>, authenticating with the license JWT as an Authorization: Bearer header. The asset name is temps-ee-<version-without-v>-linux-amd64.tar.gz. The checksum is verified before the swap.
  4. Replaces the current binary atomically.
  5. Copies the license to <data-dir>/data/license.jwt (mode 0600).
  6. Best-effort systemd wiring (Linux only): if /etc/systemd/system/temps.service exists and does not already contain a TEMPS_EE_LICENSE_PATH= line, the upgrader inserts Environment=TEMPS_EE_LICENSE_PATH=<installed-license-path> after the [Service] header and runs systemctl daemon-reload, so the binary finds its license on every restart. If the variable is already present it is left untouched; non-Linux hosts and missing units are skipped silently.

Use --check to preview the planned switch without installing, and -y/--yes to skip the confirmation prompt. After the switch, restart the service to activate:

sudo systemctl restart temps

Rolling back

Roll back to a previous version

  1. 1

    If the version you are leaving added database migrations, restore the database from your pre-upgrade backup first: docker exec -i temps-timescaledb psql -U temps temps < temps-backup-YYYYMMDD.sql (rolling back migrations is not automatic).

    Checkpoint: Confirm the restore completed without errors before swapping the binary.

  2. 2

    Downgrade the binary by pinning the target version: temps upgrade --version v1.0.0 (pinning a version bypasses channel filtering entirely).

  3. 3

    Restart the service: sudo systemctl restart temps.

Downgrade to a specific version by passing it to the upgrade command:

temps upgrade --version vX.Y.Z

If the new version added database migrations, restoring from a pre-upgrade backup is the safest path:

# 1. Restore the database backup
docker exec -i temps-timescaledb psql -U temps temps < temps-backup-YYYYMMDD.sql

# 2. Downgrade the binary
temps upgrade --version vX.Y.Z

# 3. Restart
sudo systemctl restart temps

Rolling back database migrations is not automatically supported. If a new version added migrations, restoring the database from a pre-upgrade backup is the safest way to revert.


Troubleshooting

Service fails to start after upgrade

# Check logs for errors
sudo journalctl -u temps -n 50 --no-pager   # Linux
tail -50 ~/.temps/logs/temps.log            # macOS

# Common fix: ensure data directory permissions are correct
chmod 700 ~/.temps/data
chmod 600 ~/.temps/data/encryption_key

Database migration errors

Migrations run on startup and the server will not start if they fail. Confirm the database is reachable, then read its logs:

# Check that the database is reachable
docker exec temps-timescaledb pg_isready -U temps

# Check database logs
docker logs temps-timescaledb --tail 50

If the database container is not running, start it first:

docker start temps-timescaledb

"Database migrations timed out" / crash loop after upgrade

If the server log shows something like:

Database migrations timed out after 600 seconds

and the service keeps restarting, a migration is taking longer than the boot window allows. This almost always means a new release added an index or schema change to a large table — most commonly proxy_logs, which on a busy instance can hold tens of millions of rows. The fix is to apply migrations with the server stopped and no timeout, then restart:

# 1. Stop the service so it stops crash-looping (and releases the DB)
sudo systemctl stop temps

# 2. Apply migrations explicitly with the NEW binary — this has no timeout and
#    will run a slow index build to completion. Expect minutes on a big table.
temps migrate --database-url="$TEMPS_DATABASE_URL"

# 3. Start the server — the schema is now up to date, so boot is fast
sudo systemctl start temps

# 4. Confirm
temps doctor

If proxy_logs has grown far beyond what you need, you can shrink it before migrating so the index build finishes faster. Check the size first:

docker exec temps-timescaledb psql -U temps -d temps \
  -c "SELECT count(*) FROM proxy_logs;"

To drop old request-log data (this is analytics/observability data, not your application data), drop chunks older than a cutoff — for example keep the last 7 days:

docker exec temps-timescaledb psql -U temps -d temps \
  -c "SELECT drop_chunks('proxy_logs', older_than => now() - interval '7 days');"

Was this page helpful?