Skip to contentLaunch price30% off every kit for the first 250 buyers155 left
DirectoryLab

Loading the guides…

Launch LabGuides
Keep it running

Updating Launch Lab

Move a site to a new version without losing its data or its settings, on a server or on your own computer, with repository access or with a zip.

Updated 9 Oct 2026 · written for Launch Lab 2.9.0

On this page

A new version of Launch Lab is new code. Your listings, accounts, settings and uploads are in a few files and folders that the code never touches, so an update means replacing the code round them. The database then brings itself up to date when the site starts. How you update depends on how the site runs: under deploy.sh, under Docker Compose or without Docker.

Which version you run#

Open System under Tools in the admin sidebar. The Status panel shows the Version, the Database in use, the Schema revision and Debug mode. Version 2.9.0 has schema revision 0012.

The Status panel on the System page: the version, the database and whether it is connected, the schema revision and whether debug mode is on
The version, the database and the schema revision, on the System page.

The version is also at the foot of every admin page, beside System status, and in the file app/version.py.

What is yours#

Keep these through every update. None of them is in the zip or the repository.

File or folderWhat is in it
instance/The database app.db, the uploads folder and, if the site made its own key, the secret_key file.
.envYour settings.
sites/On a deploy.sh server: each site's .env, port, domain and instance folder.
backups/ and .backup.envOn a deploy.sh server: the nightly archives and the Telegram alert settings.
Packs and kits you addedYour own folders in niches/ and brands/.
Files you changedA page template you edited, or scripts/nginx-site.conf.template. Note which: you make the change again in the new copy.

Before you update#

  • Read CHANGELOG.md in the new version. Each entry ends with a part headed "Upgrading" that says what, if anything, you must do.

  • Take a backup. On a deploy.sh server run scripts/deploy.sh backup: see Backups and scheduled jobs. Anywhere else, stop the site and copy the instance folder and .env.

  • Leave SECRET_KEY as it is. The keys saved on Integrations are locked with it.

On a server set up with deploy.sh#

With repository access, one command fetches the code and updates every site:

bash
cd /opt/launchlab
scripts/deploy.sh backup
scripts/deploy.sh update

With a zip there is nothing to fetch from, so copy the new files over the old ones first, then run the same command:

On your computer
scp launch-lab-X.Y.Z.zip root@YOUR-SERVER-IP:/opt/
On the server
cd /opt
unzip launch-lab-X.Y.Z.zip
cp -R launch-lab-X.Y.Z/. /opt/launchlab/
cd /opt/launchlab
scripts/deploy.sh backup
scripts/deploy.sh update

The copy replaces the kit's own files and leaves sites/, backups/ and .backup.env alone. A file of the kit's that you had changed is replaced: make your change again before you run update.

What update does#

update asks no questions. It does these things, in this order:

  1. Checks the server. It stops if another deploy is running, if a git checkout has changes that were never committed or if the disk is 90% full or more.

  2. Fetches the code, in a git checkout: git pull --ff-only for the branch you are on. Without git it uses the files as they are.

  3. Keeps the running build. The image the sites run from now is given the name launchlab:previous. Then the new image is built.

  4. Takes each site in turn, in name order:

    • Copies its SQLite database to sites/<name>/instance/.snapshots/. The newest five are kept.

    • Brings the database up to date in a throwaway container while the old site is still serving, and checks that it got there. It prints a line such as "database at 0012, code expects 0012".

    • Replaces the site's container with one from the new image. The site does not answer for the moments in between.

    • Waits for /health to answer, a few minutes at most, then prints "gadgets is healthy."

  5. Tidies up. It installs the cron lines again, removes its own old images and trims Docker's build cache to 3 GB.

  6. Reports. It says whether the other containers on the server were left unchanged, how full the disk is and the state of every site, and ends "DEPLOY OK."

If the database or the health check fails for a site, update stops there and goes back by itself. That site and every site updated before it are started again from launchlab:previous, each with its snapshot put back. Sites it had not reached were never touched. It ends "DEPLOY FAILED at 'gadgets' and was rolled back. Every site is running the previous build."

When update refuses or fails#

  • "This checkout has uncommitted changes. Deploys only run from clean code": you changed one of the kit's own files on the server, such as the nginx template. Files you added do not count. Run git status to see which. Put the change aside with git stash, run update, then bring it back with git stash pop. A change put aside is not in the image that gets built. That is fine for the nginx template, which is read on the server. A changed page template must be committed to the repository you pull from.

  • "The disk is 91% full. Free some space before deploying.": free some space. Old archives in backups/ are the usual place to look.

  • "Another deploy is running on this server.": wait for it to finish.

  • The fetch fails: the script never waits for a password, so the server must be able to pull without one. It also fails if the server's copy has commits the repository does not.

  • "The database of gadgets could not be brought up to date.": the reason is printed above that line. The sites have gone back to the previous build.

  • "gadgets did not become healthy on the new build.": the new container is removed when the site goes back, and its log goes with it. Run the update again when few people are about and watch scripts/deploy.sh logs gadgets from a second terminal.

With Docker Compose#

bash
cd /opt/launchlab
git pull
docker compose up -d --build

With a zip, copy the new files over the old ones as above in place of git pull. The second line builds the new image and restarts the three containers. The database is brought up to date as the site starts. There is no snapshot and no going back here: copy the instance folder first.

Without Docker, and on your own computer#

  1. Step 1: Stop the site and copy what is yours

    Copy the instance folder and .env somewhere outside the kit's folder.

  2. Step 2: Replace the code

    With repository access, run git pull in the kit's folder. With a zip, unpack it beside the old folder and copy its contents over:

    bash
    cp -R ../launch-lab-X.Y.Z/. .

    Your instance folder, your .env and your virtual environment are not in the zip, so they stay as they are.

  3. Step 3: Install the packages

    bash
    pip install -r requirements.txt

    Run it with the virtual environment active. It adds whatever the new version needs and does nothing when there is nothing new.

  4. Step 4: Start the site

    Start it the way you always do: python run.py on your computer, or your gunicorn service on a server. Open System and check that Version shows the new number.

The background jobs on the System page: each job with what it does, how often it runs, when it last ran, its result and a Run now button
Admin, System

After any update, look at Background jobs on the same page a little later: the Last run (UTC) times should still be moving.

How the database catches up#

Each version carries its database changes as numbered files in migrations/versions. The newest in 2.9.0 is 0012. Every time the app starts, whether as the site, the job runner or a script, it applies the files your database has not had yet. There is no command to run.

  • To check, compare Schema revision on the System page with the highest number in migrations/versions. The address /health gives the same revision.

  • If a change fails, the app writes a line beginning "Database bootstrap failed:" and starts anyway, on a database that does not match the code. deploy.sh update checks for this and goes back. Under Compose or without Docker, look for that line yourself after an update.

  • To do it by hand, set AUTO_INIT_DB=false in .env. The app then leaves the database alone, and you run flask --app run.py db upgrade after each update.

The stylesheet#

The site's stylesheet, app/static/css/tailwind.css, comes ready built, and an update brings the new one. You need Node.js only if you changed the class names in a template, because the stylesheet holds only the classes the templates use. Rebuild it with:

bash
npm install
npm run build-css-prod

npm run build-css builds the same file and keeps watching for changes while you work. An update replaces the built file with the kit's own, so rebuild yours after each one. Under Docker the stylesheet is copied into the image when the image is built: with deploy.sh and git, your rebuilt file must be committed to the repository you pull from.

What 2.9.0 and 2.8.0 changed#

VersionWhat changedWhat to do
2.9.0Payments work with the current version of Stripe's library: before, a new install could not take a payment, a purchase that was paid was never applied, and vouchers could not be created, changed or deleted. The setup wizard has a new look, with its seven steps down the side and a summary before it creates the site, and it loads nothing from another site. Editing a custom email template works: it used to open a page that did not exist. A voucher records the discount you typed: it used to say 50 whatever you entered. A voucher that Stripe has no coupon for is now refused, where before a maker saw the lower price and was charged the full one. Send to all users on a campaign is saved, and the filters on the email Logs page work.No database change. Run pip install -r requirements.txt, which scripts/deploy.sh update does for you: Stripe's library is now pinned. Then make one test purchase and check it is applied. If you made a voucher while Stripe was not connected, delete it and create it again.
2.8.0The Signature tab of Site Settings became Footer credits. Beside your signature there is an optional "Built with" credit, a choice of how the two are arranged and a preview.Database change 0012 runs at the first start. The new credit is off until you switch it on.
How do I go back to the old version?

A failed update goes back by itself. To go back after one that worked, check out the older commit with git and run scripts/deploy.sh update: it says "Checkout is pinned to" and the commit, and deploys exactly that. This works only when the newer version made no database change. From 2.9.0 to 2.8.0 it does. Across a database change, restore the backup you took before the update: see Restore a backup.

Do I have to run flask db upgrade?

No. The app does that itself when it starts, unless you set AUTO_INIT_DB=false.

Will an update change my wording, prices or brand?

No. Those are in the database, which an update adds to and does not reset. Only files are replaced.

Is the site down during an update?

Under deploy.sh, each site stops answering for the moments its container is swapped, one site at a time. Under Compose and without Docker the site is down from the restart until it is up again.

Can I update one site and not the others?

Not with deploy.sh. Every site on the server runs from the same image, and update moves them all.

Stuck on a step? Send a message.