Upgrade
Follow the steps for your installation method. The procedure differs depending on how you installed Tradeboard.
DANGER
Never run cp .sample.env .env on an existing installation. It erases your broker keys and replaces your API_KEY_PEPPER, which permanently invalidates every stored password hash and encrypted broker token. Your .env is not tracked by git, so git pull always preserves it. When an update introduces new environment variables, copy only those new lines from .sample.env into your existing .env (see "Handling new environment variables" below).
Option 1: Ubuntu Server (installed via install.sh)
Your installation lives at /var/python/tradeboard and runs as the systemd service tradeboard. Use the bundled update script, it performs the entire upgrade safely in one command:
cd /var/python/tradeboard
sudo bash install/update.shThe script automatically:
- Stops the
tradeboardservice - Backs up all databases (tradeboard, logs, latency, sandbox, historify) to a timestamped
db/backup_<timestamp>/folder - Pulls the latest code (
git pull) - Compares your
.envagainst.sample.envand lists any new variables you should add (it never overwrites your.env) - Updates Python dependencies
- Runs all database migrations
- Restarts the service
Legacy multi-deployment installs (/var/python/tradeboard-flask/<name>/tradeboard/) are detected automatically and the correct tradeboard-<name> service is updated.
WARNING
update.sh does not detect instances created by the current install/install-multi.sh, which lays them out as /var/python/tradeboard-flask/tradeboard1, tradeboard2 and so on with services named tradeboard1, tradeboard2. Update those by hand:
cd /var/python/tradeboard-flask/tradeboard1
sudo cp -r db db-backup-$(date +%Y%m%d)
sudo git pull
sudo uv pip install --python venv/bin/python -r requirements-nginx.txt
sudo bash -c "source venv/bin/activate && python upgrade/migrate_all.py"
sudo systemctl restart tradeboard1Pre-built images on Docker Hub
CI publishes a multi-architecture image (amd64 and arm64) on every push to main:
wesoftcorp/tradeboard-docson Docker Hubwesoftcorp/tradeboard-docs:latest, the currentmainwesoftcorp/tradeboard-docs:<commit-sha>, every build, so you can pin or roll back to an exact version
Which upgrade path applies to you depends on how you installed:
| Install method | Image source | Upgrade |
|---|---|---|
docker-run.sh (Desktop) | Pulls wesoftcorp/tradeboard-docs:latest | ./docker-run.sh pull then ./docker-run.sh restart |
install-docker.sh (custom domain) | Builds locally from the repo | git pull then rebuild, Option 2a |
install-docker-multi-custom-ssl.sh | Builds locally per instance | Re-run the installer in update mode, Option 2b |
| Manual clone | Builds locally | Option 2 |
The server install scripts build locally rather than pulling, so a docker pull alone will not update them. If you would rather pull than build, it is much faster, since no build step runs, see "Using the pre-built image instead of building" below.
Option 2: Docker: manual clone (no custom domain)
Use this if you cloned Tradeboard yourself and run it with docker compose.
# From the folder where you cloned tradeboard
cd ~/tradeboard
# Pull the latest code (includes the pre-built frontend)
git pull
# Rebuild and restart the container
sudo docker compose down
sudo docker compose build --no-cache
sudo docker compose up -d
# Watch startup logs
sudo docker compose logs -fYour .env file and the named volumes persist across the rebuild, no reconfiguration needed. Database migrations run automatically on container startup.
WARNING
If you installed with install-docker.sh or install-docker-multi-custom-ssl.sh, your installation is NOT in ~/tradeboard. It is under /opt/tradeboard. Use Option 2a or 2b below instead.
Option 2a: Docker + Custom Domain (single instance)
For installations created with install-docker.sh, which sets up nginx, a Let's Encrypt certificate and your domain.
Your installation lives at /opt/tradeboard.
cd /opt/tradeboard
# 1. Back up the database volume first (see "Backing up" below)
# 2. Pull the latest code
sudo git pull
# 3. Rebuild and restart
sudo docker compose down
sudo docker compose build --no-cache
sudo docker compose up -d
# 4. Watch it come up
sudo docker compose logs -fWhat is preserved automatically, and why:
| Item | Location | Survives because |
|---|---|---|
Your .env | /opt/tradeboard/.env | Not tracked by git, bind-mounted into the container |
| Database, logs, strategies, keys | Docker named volumes | Volumes are independent of the image |
| nginx site configuration | /etc/nginx/sites-available/<domain> | Outside the repository entirely |
| SSL certificate | /etc/letsencrypt/live/<domain>/ | Outside the repository entirely |
Because nginx and the certificate live outside the repo, a Docker upgrade cannot break your domain or SSL. You do not need to re-run certbot, and you do not need to touch nginx.
You also do not need to change your broker's redirect URL, it stays https://<your-domain>/<broker>/callback.
Option 2b: Docker + Custom Domain (multi-instance)
For installations created with install-docker-multi-custom-ssl.sh, where each domain is its own instance under /opt/tradeboard/<domain>.
The installer doubles as the upgrade script. Re-run it and choose update mode:
cd /opt/tradeboard
sudo bash install-docker-multi-custom-ssl.shWhen it finds an existing instance it asks:
Instance for yourdomain.com already exists. Update code only? (y=update, n=skip, r=reinstall):Answer y. It pulls the latest code and preserves your existing configuration, broker keys, API secrets and domain settings are read back out of the instance's .env and reused. Answer n to skip an instance you do not want to touch.
To upgrade a single instance by hand instead:
cd /opt/tradeboard/yourdomain.com
sudo git pull
sudo docker compose down
sudo docker compose build --no-cache
sudo docker compose up -dINFO
Each instance has its own directory, its own .env, its own volumes and its own nginx site. Upgrading one does not affect the others, which is also why you must repeat the steps for each domain you want on the new version.
Using the pre-built image instead of building
Optional. Building locally takes several minutes and needs build tooling on the server; pulling the published image takes seconds. The trade-off is that you give up local modifications to the source.
Edit docker-compose.yaml in your install directory:
services:
tradeboard:
image: wesoftcorp/tradeboard-docs:latest # was: tradeboard:latest
# build: # comment out or delete this block
# context: .
# dockerfile: DockerfileThen upgrades become:
cd /opt/tradeboard
sudo docker compose pull
sudo docker compose up -dPinning to an exact version is the main practical benefit, it makes rollback immediate and unambiguous:
image: wesoftcorp/tradeboard-docs:a1b2c3d # a specific commit SHAsudo docker compose pull && sudo docker compose up -dINFO
Keep git pull in your routine even when using the pre-built image. The repository still supplies docker-compose.yaml, the installer scripts and the migration files, and you want those current alongside the image.
WARNING
Only main is published as latest. If you are testing a branch, you must build locally, there is no published image for it.
Backing up before a Docker upgrade
Named volumes are not touched by docker compose build, but take a copy before any upgrade.
The simplest method, copy straight out of the running container:
cd /opt/tradeboard
sudo docker compose cp tradeboard:/app/db ./db-backup-$(date +%Y%m%d)Here tradeboard is the compose service name, not the container name.
If you prefer to archive the volume itself, look up its real name first:
sudo docker volume ls | grep dbDocker Compose prefixes volume names with the project name, which is taken from the directory. An install in /opt/tradeboard produces tradeboard_tradeboard_db, while /opt/tradeboard/yourdomain.com produces something different. Always read the name rather than assuming it:
VOL=$(sudo docker volume ls --format '{{.Name}}' | grep 'tradeboard_db$' | head -1)
echo "Backing up volume: $VOL"
sudo docker run --rm \
-v "$VOL":/data \
-v "$(pwd)":/backup \
alpine tar czf /backup/db-backup-$(date +%Y%m%d).tar.gz -C /data .WARNING
Check that your backup is not empty. If you pass a volume name that does not exist, Docker silently creates a new empty volume and the archive succeeds with nothing in it:
ls -lh db-backup-*.tar.gz # a few KB means it is empty
tar tzf db-backup-*.tar.gz | head # should list tradeboard.db and friendsAlso copy your .env, which is the only file that cannot be regenerated:
sudo cp /opt/tradeboard/.env /opt/tradeboard/.env.backup-$(date +%Y%m%d)Rolling back a Docker upgrade
cd /opt/tradeboard
# Go back to the previous commit
sudo git log --oneline -5 # find the commit you were on
sudo git checkout <previous-commit>
sudo docker compose down
sudo docker compose build --no-cache
sudo docker compose up -dRestore the database only if the upgrade actually migrated it and you need the earlier schema, migrations are forward-only, so a rollback of code without the matching database can fail to start.
After a Docker + custom domain upgrade
Check these in order:
# Container healthy
sudo docker compose ps
# App answering locally
curl -I http://127.0.0.1:5000/auth/check-setup
# Domain and certificate still good
curl -I https://yourdomain.com
# Certificate expiry unchanged
sudo certbot certificatesThen in the browser: log in, confirm the dashboard loads, and confirm live data updates, a working page with frozen prices means the WebSocket upgrade through nginx is not working, which is the one thing worth checking specifically on a custom-domain setup.
Option 3: Desktop / Local: Update Scripts (Recommended)
Tradeboard runs cross-platform, Windows, macOS and Linux desktops all have a one-command update path using the bundled scripts.
Windows
cd tradeboard
install\update.batmacOS / Linux
cd tradeboard
bash install/update.shBoth scripts detect a local (non-server) installation automatically and perform the same safe sequence: back up all databases (tradeboard, logs, latency, sandbox, historify) to a timestamped db\backup_<timestamp>\ folder → git pull → report any new .env variables → update dependencies via uv → run all database migrations. Your .env is never touched.
After the script finishes, start Tradeboard as usual:
uv run app.pyYou should see the configuration check pass and all databases initialize:
Configuration version check passed
INFO in telegram_db: Telegram database initialized successfully
INFO in base: Scheduler started
INFO in traffic_db: Initializing Traffic Logs DB at: sqlite:///db/logs.db
INFO in latency_db: Initializing Latency DB at: sqlite:///db/latency.db
INFO in auth_db: Initializing Auth DB
INFO in symbol: Initializing Master Contract DBOption 4: Manual Upgrade (any platform)
If you prefer to run the steps yourself, only the backup step differs by platform, everything else is identical on Windows, macOS and Linux.
1. Backup the databases
Windows (PowerShell):
cd tradeboard
New-Item -ItemType Directory -Force db\backup | Out-Null
Copy-Item db\*.db db\backup\
Copy-Item db\historify.duckdb db\backup\ -ErrorAction SilentlyContinuemacOS / Linux:
cd tradeboard
mkdir -p db/backup
cp db/*.db db/backup/ 2>/dev/null
cp db/historify.duckdb db/backup/ 2>/dev/null2. Pull the latest application code
git pull3. Update dependencies
uv sync(Legacy pip setups: pip install -r requirements.txt inside your virtual environment.)
4. Run the migration script
Migrations are idempotent, safe to run on every upgrade. From the project root:
uv run upgrade/migrate_all.pyOr use the equivalent command from the upgrade directory:
cd upgrade
uv run migrate_all.pyThe Strategy RMS migration is required on releases that include it. It creates the sm_ strategy tables on an older installation and adds compatible nullable columns/indexes to an earlier Strategy RMS schema. Existing rows are retained; unknown historical values are not invented. If this required migration fails, the runner exits non-zero: do not start the updated application until the failure is resolved.
5. Start Tradeboard
uv run app.pyHandling new environment variables
Newer releases often introduce new environment variables that Tradeboard needs to function. Do not recreate your .env from the sample, merge instead:
- Open
.sample.envand checkENV_CONFIG_VERSIONat the top. If it is newer than the version in your.env, new variables were added. - On startup, Tradeboard's configuration check reports exactly which variables are missing.
- Copy only the missing lines from
.sample.envinto your.envand set appropriate values, keeping all your existing settings (broker keys,APP_KEY,API_KEY_PEPPER, domain configuration) untouched.
The update scripts (install/update.sh on Linux/macOS, install\update.bat on Windows) print this comparison for you automatically.
WARNING
Never copy APP_KEY or API_KEY_PEPPER values from documentation or .sample.env into a real installation. Generate your own:
python -c "import secrets; print(secrets.token_hex(32))"And never change API_KEY_PEPPER on an installation that already has users or broker logins, it is used to hash passwords and encrypt broker tokens, and rotating it makes that data unrecoverable. If you genuinely need to rotate it, use the dedicated migration: uv run python upgrade/rotate_pepper.py.
Post-Upgrade Checklist
- Configuration version check passes on startup (no missing variable warnings)
- All databases initialize correctly
- Scheduler, WebSocket proxy (port 8765) and ZeroMQ services are running
- Broker login works and the master contract downloads
- API requests and strategies function normally
If anything fails after an upgrade, check log/errors.jsonl first, it contains structured error details including full tracebacks.
Optional: testing the gthread worker
Tradeboard currently runs on Gunicorn's eventlet worker, which is retired software, Gunicorn 26 removes it entirely. An experimental migration to the threaded gthread worker is available for testing.
It is opt-in and not the default: a normal upgrade leaves you on eventlet with no change in behaviour.
If you would like to help test it, see Migrating to gthread (Experimental).
