How-to · Install and run
Update, back up and move a NetSendo installation
Back up NetSendo's database, uploaded files and .env, update to a new release with Docker Compose, restore a backup and move the installation to a new server.
For NetSendo 2.7.0, checked against its source code on 2026-11-08.
In short
Back up three things: a mysqldump of the database, the files in the netsendo-storage volume, and .env with its APP_KEY. To update, refresh docker-compose.yml and the files it mounts, set NETSENDO_VERSION, then run docker compose pull and docker compose up -d; the app container runs the migrations. To move, import the dump and the files on the new server, using the same .env.
Before you start
- You need a shell on the server. Run every command in the installation folder, the one that holds
docker-compose.ymland.env. That isNetSendofor a git clone andnetsendofor aninstall.shinstall. - Check how NetSendo was installed. A git clone gets new configuration files with
git pull. Aninstall.shfolder has no git history, so you download them instead. - No licence plan is needed for anything on this page.
- The short versions are in Quick Start → Updating and Backups. This page adds restoring and moving.
- Settings → Updates, tab How to Update, shows a shorter list: stop, pull, start. That list has no backup step and leaves the configuration files as they are. The steps below follow
DOCKER_INSTALL.mdof 2.7.0.
What to keep:
| What | Where it lives | Why |
|---|---|---|
| Database | volume dbdata |
contacts, lists, campaigns, settings, licence |
| Uploaded files | volume netsendo-storage (/var/www/storage/app) |
images, attachments, NetSendo's own backup archives |
.env |
installation folder | APP_KEY decrypts the saved mailbox, SMS and AI credentials |
| Changed configuration files | docker-compose.yml, docker/ |
only if you edited them |
You can skip redisdata, netsendo-public and netsendo-logs. redisdata does hold the job queue, though, so let the queue finish before you move the installation (Move to another server, step 1).
Steps
Back up
- Dump the database into
backups/db/(the db container mounts that folder at/backups):docker compose exec db sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction "$MYSQL_DATABASE" > /backups/netsendo.sql' - Pack the uploaded files:
The archive includes NetSendo's own backups, indocker compose exec -T app tar czf - -C /var/www/storage app > backups/netsendo-storage.tar.gzapp/private/NetSendowith the defaultAPP_NAME. To leave them out, add--exclude=app/private/NetSendoafterczf -. - Copy
.env:cp .env backups/env.backup - Copy the
backupsfolder to another machine. The dump holds all your contact data, so keep it private.
To copy the dbdata volume itself instead of dumping it, stop the stack first. Restore such a copy only with the same MySQL image (mysql:8.4) and the same .env:
docker compose stop
docker run --rm --volumes-from netsendo-db -v "$PWD/backups":/out ubuntu tar czf /out/dbdata.tar.gz --numeric-owner -C /var/lib/mysql .
To put it back into a new, empty volume, run docker compose create db, then docker run --rm --volumes-from netsendo-db -v "$PWD/backups":/out ubuntu tar xzf /out/dbdata.tar.gz --numeric-owner -C /var/lib/mysql, then docker compose up -d.
NetSendo also has its own backups under Settings → Backups. Create Backup adds an archive to Available Backups. With Database only (faster backup) ticked (the default) the archive holds the database; untick it to include the application files too. The scheduler makes a full backup every day at 02:00 UTC and removes old ones at 03:30 UTC. These archives are stored in netsendo-storage on the same server, so click Download to keep a copy somewhere else.
Update to a new release
- Dump the database (Back up, step 1). Migrations run when the app starts, and they cannot be undone.
- Update the configuration files. In a git clone run
git pull(orgit fetch --tagsandgit checkout v2.7.0to match a pinned release). In aninstall.shfolder, download them (your old copies stay as.bak):
If you had changed one of these files, carry your change over from itsV=v2.7.0 for f in docker-compose.yml docker/php/docker-entrypoint.sh docker/php/uploads.ini docker/nginx/default.conf; do cp "$f" "$f.bak" 2>/dev/null curl -fsSL "https://raw.githubusercontent.com/NetSendo/NetSendo/$V/$f" -o "$f" done chmod +x docker/php/docker-entrypoint.sh.bakcopy. - In
.env, setNETSENDO_VERSION=2.7.0, or add the line if it is missing.install.shwrites this line itself. Without it the image tag islatest, which is built from the main branch whenever its code changes, not only at releases. - Compare
.envwith the release's.env.examplefor new settings..envis never replaced. - Pull the images and start:
Thedocker compose pull docker compose up -d docker compose logs -f appappcontainer runs the pending migrations.scheduler,queueandreverbwait until it has finished. Press Ctrl+C to stop following the log. You do not needdocker compose down, a frontend rebuild or clearing the cache.
Restore
- Stop the containers that use the database:
docker compose stop app scheduler queue reverb - Empty the database, then import the dump:
The database user keeps its rights; MySQL does not remove them when a database is dropped.docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" -e "DROP DATABASE $MYSQL_DATABASE; CREATE DATABASE $MYSQL_DATABASE;"' docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /backups/netsendo.sql' - To go back to the release you had before an update, set its number in
NETSENDO_VERSIONand put back the.bakcopies, or the previous tag in a git clone. - Start everything with
docker compose up -d. - If you also need the files back:
docker compose exec -T app tar xzf - -C /var/www/storage < backups/netsendo-storage.tar.gz
Settings → Backups → Restore does the database part in the panel. It first makes a safety backup of the current database, then replaces the database, but not the files, with the archive's. The queue worker must be running.
Move to another server
- On the old server, stop new sends:
docker compose stop scheduler. - Let the queue run empty. With the default
QUEUE_CONNECTION=redis,docker compose exec app php artisan queue:monitor redis:default,redis:notificationsshows the number of waiting jobs. When both are0, rundocker compose stop queue. - Make the three backups (database, files,
.env) as above, then rundocker compose stop. Signups and unsubscribes that reach the old server after the dump are not copied, so keep this window short. - Copy the whole installation folder,
backupsincluded, to the new server. For example:rsync -a ./ user@new-server:/opt/netsendo/. - On the new server, install Docker Engine with the Compose plugin (Docker's install guide).
- In the copied
.env, keepAPP_KEY,DB_DATABASE,DB_USERNAME,DB_PASSWORDandNETSENDO_VERSIONas they are. ChangeAPP_URLonly if the address changes. - Start only the database:
docker compose up -d db. Wait untildocker compose ps dbshowshealthy. - Import the dump:
docker compose exec db sh -c 'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" < /backups/netsendo.sql' - Start the app:
docker compose up -d app. Then put the files back with thetar xzfcommand from Restore, step 5. - Start the rest:
docker compose up -d. - Set up the reverse proxy and HTTPS (Configuration → HTTPS), then point DNS at the new server.
- Do not start the old stack again. Each server has its own copy of the database, so both would send the same scheduled messages.
If you use the nmi profile, also copy its DKIM keys and TLS certificates. On the old server, run docker run --rm --volumes-from netsendo-mta -v "$PWD/backups":/out ubuntu tar czf /out/nmi.tar.gz --numeric-owner /opt/haraka/config/dkim /opt/haraka/config/tls. On the new server, run docker compose --profile nmi create nmi-mta, then the same docker run with tar xzf /out/nmi.tar.gz --numeric-owner -C /. Then see SPF, DKIM and DMARC for the new IP address.
Check that it works
docker compose pslists every service as running, withapp,webserver,dbandredismarkedhealthy.docker compose exec app php artisan migrate:statusshows no line markedPending.- In the panel, open Settings → Updates. Current Version shows the release, for example
v2.7.0. - An AI assistant connected through the NetSendo MCP server can confirm the version with
get_account_info; the answer includesversion. - After a move, sign in at the new address, open a message with images, and send a test email.
head -n 3 backups/db/netsendo.sqlstarts with aMySQL dumpcomment, andtar tzf backups/netsendo-storage.tar.gz | headlistsapp/…paths.
If it doesn't work
Access denied for user 'root' when you dump or import
MySQL sets the root password from DB_PASSWORD only the first time it starts on an empty volume. A later change to .env does not change it. Put back the password the database was created with.
scheduler, queue or reverb keep logging "Waiting for the app container to migrate the database..."
The app is still migrating, or a migration failed. Read docker compose logs app. After 1,800 seconds (NETSENDO_MIGRATIONS_WAIT_TIMEOUT) they start anyway.
The app log says "migration(s) still pending after 3 attempts"
A migration failed. The error is in the lines above. Restore the dump and stay on the old release until the problem is fixed.
The version did not change after docker compose pull
NETSENDO_VERSION in .env still names the old release.
A container does not start, and docker/php/docker-entrypoint.sh or uploads.ini is a folder
A file that docker-compose.yml mounts was missing, so Docker created a folder in its place. Delete the folder, download the file (Update, step 2), then run docker compose up -d.
"Table … already exists" after a move or restore
The app migrated the empty database before the import, using a newer release than the dump. Empty the database (Restore, step 2), import again, then start.
Mailboxes fail to log in, or AI and SMS keys stop working, after a move
APP_KEY differs from the old server. NetSendo cannot decrypt the saved credentials. Put the old key back and run docker compose up -d --force-recreate.
Settings → Backups stays on "Creating backup…" or says "Another backup operation is already running"
The backup runs on the queue. Start the worker with docker compose up -d queue and check docker compose logs queue.
A backup fails with a TLS/SSL error
Keep MYSQL_DUMP_SSL=false in .env, as in .env.example.
Related how-to guides
Sources
- Docker: Back up, restore, or migrate data volumes (checked 2026-11-08)
- Docker Official Image: mysql (environment variables apply only to an empty data directory) (checked 2026-11-08)
- MySQL 8.4 Reference Manual: DROP DATABASE (checked 2026-11-08)
- Docker: Install Docker Engine (checked 2026-11-08)
Something different in your panel? Your instance may run another release; see the release notes or write to support@netsendo.com. Every page of these docs is also available as Markdown for AI assistants: request https://netsendo.com/en/docs/how-to/update-backup-migrate with Accept: text/markdown.

