Quick Start
Install self-hosted NetSendo with Docker Compose: one repository, one .env file and one command.
Written for NetSendo 2.7.0, following install.sh, DOCKER_INSTALL.md and docker-compose.yml of the repository.
Requirements
| Minimum | Recommended | |
|---|---|---|
| Docker | 20.10 | 24.0 |
| Docker Compose | v2.0 | v2.20 |
| RAM | 2 GB | 4 GB |
| Disk space | 5 GB | 10 GB |
You also need Git and a terminal on the server.
1. Clone the repository
git clone https://github.com/NetSendo/NetSendo.git
cd NetSendoinstall.sh creates a netsendo folder with the configuration files, writes a .env with a new APP_KEY and a random database password, starts the stack on http://localhost:5029 and waits until it answers. Set VERSION=2.7.0 before bash to install a specific release.curl -fsSL https://raw.githubusercontent.com/NetSendo/NetSendo/main/install.sh | bash2. Create the configuration file
cp .env.example .env
# Put a new key on the APP_KEY line
sed -i.bak "s|^APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|" .env
# Set APP_URL and a strong DB_PASSWORD
nano .envThe settings to fill in before the first start:
APP_KEY=base64:YOUR_GENERATED_KEY
APP_URL=http://localhost:5029
DB_DATABASE=netsendo_prod
DB_USERNAME=netsendo_user
DB_PASSWORD=YOUR_SECURE_PASSWORDAPP_KEYencrypts stored passwords and keys. Set it once and keep it: the containers mount.envread-only, so they cannot write a key into it for you.DB_PASSWORDis also the MySQL root password. MySQL takes it on its very first start, so choose it now.APP_URLis the address people will use. Keephttp://localhost:5029for a local test; for a server, use its HTTPS address (see going live).
.env does not exist when you run docker compose up -d, Docker creates a folder named .env in its place and the application does not start.The other variables are explained in Configuration.
3. Start NetSendo
docker compose up -d
# Watch the first start (migrations run automatically)
docker compose ps
docker compose logs -f appThe first start pulls the ghcr.io/netsendo/netsendo image and starts these services: the application, the queue worker, the scheduler, the Reverb WebSocket server, nginx, MySQL 8.4, Redis 7 and Mailpit. The application creates the database tables on its first start.
| Service | Address |
|---|---|
| NetSendo | http://localhost:5029 |
| Mailpit (test emails) | http://localhost:5031 |
| Reverb (WebSocket) | http://localhost:8085 |
4. Create the administrator and activate a license
- Open http://localhost:5029 and register. The first account becomes the administrator; after that, registration is closed and the administrator adds further users.
- The license page opens. For SILVER (free), enter your email address and the license is activated automatically.
- For GOLD, choose Buy GOLD: payment opens on netsendo.com in a new tab, and GOLD switches on as soon as the payment is confirmed. A key you already have goes in under I have a license key.
Then add a mailbox under Settings → Mailboxes so NetSendo can send, and an SMS provider if you need SMS. Until then, a test send of a campaign fails with “No mailbox is configured”, and emails NetSendo sends through the MAIL_* settings, such as team invitations, land in Mailpit.
5. Going live on a server
- NetSendo listens on
127.0.0.1:5029only. Put a reverse proxy with HTTPS (nginx or Caddy) in front of it and forward/app/to port 8085 for WebSockets. - Set
APP_URLto the HTTPS address,VITE_REVERB_HOSTto your domain,VITE_REVERB_PORT=443andVITE_REVERB_SCHEME=https, then rundocker compose up -d --force-recreate. The image already contains the built frontend; do not runnpm run buildin the container. - Point the
MAIL_*variables at a real SMTP server instead of Mailpit. - Register the administrator before the instance is reachable from the internet.
The nginx configuration and all variables are in Configuration → HTTPS and a reverse proxy.
Updating
cd NetSendo
git pull # new docker-compose.yml, docker-entrypoint.sh and nginx config
docker compose pull
docker compose up -d
docker compose logs -f app # migrations run here; scheduler, queue and reverb wait for themBack up the database first (see Backups): the app container runs the database migrations when it starts, and they cannot be undone. The scheduler, queue and Reverb containers wait until the migrations are done. To stay on one release instead of latest, set the image version:
NETSENDO_VERSION=2.7.0 docker compose up -ddocker compose pull updates the images, not your docker-compose.yml or docker/php/docker-entrypoint.sh, which the compose file mounts over the image's own copy. In a git checkout git pull brings them. An installation made with install.sh has no git checkout; download them from the release instead (your old copies stay as .bak files, so you can carry over your own changes). The script also keeps the image version in .env, so move NETSENDO_VERSION to the new release or to latest:
cd netsendo
V=main # or a release tag such as 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
# install.sh wrote the installed version into .env: move it to the new release (or latest)
sed -i.bak "s|^NETSENDO_VERSION=.*|NETSENDO_VERSION=2.7.0|" .env
docker compose pull
docker compose up -dRead the release notes for changes to that file, such as the queue worker fix described in Configuration.
Backups
# Database (the db container knows its root password and database name)
docker compose exec -T db sh -c 'exec mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' > backup.sql
# Uploaded files
docker compose cp app:/var/www/storage/app ./backup-storage/
# Restore the database
docker compose exec -T db sh -c 'exec mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' < backup.sql| Volume | Holds | Back up? |
|---|---|---|
| dbdata | MySQL database | Yes |
| netsendo-storage | Uploaded files | Yes |
| netsendo-logs | Application logs | Optional |
| redisdata | Redis cache and queue | No |
| netsendo-public | Static files, synced on every start | No |
Keep a copy of .env too: without its APP_KEY, encrypted data in the database cannot be read. NetSendo also runs its own backup of the database and files every night at 02:00 through the scheduler.
Optional components
- NetSendo Mail Infrastructure, a built-in MTA for sending from dedicated IPs:
docker compose --profile nmi up -d. See NMI. - MCP server for Claude, Cursor and VS Code: see the MCP documentation.
Troubleshooting
A .env folder was created instead of a file
docker compose down
rm -rf .env
cp .env.example .env
# set APP_KEY, APP_URL and DB_PASSWORD again, then
docker compose up -dThe app container does not start
docker compose logs app
docker compose exec db mysqladmin ping -h localhost
docker compose exec app php artisan config:clearWebSocket errors in the browser console usually mean the reverb container is not running or the VITE_REVERB_* values do not match your address; rebuild the frontend after correcting them. More cases are in DOCKER_INSTALL.md.
Frequently asked questions
What are the system requirements for NetSendo?
Docker 20.10 or newer with Docker Compose v2, Git, at least 2 GB of RAM and 5 GB of disk space. 4 GB of RAM and 10 GB of disk are recommended, with Docker 24 and Compose v2.20 or newer.
Which port does NetSendo run on?
The web interface is on http://localhost:5029 (HTTP_PORT), bound to 127.0.0.1. Mailpit, which catches test emails, is on http://localhost:5031, and the Reverb WebSocket server on port 8085. For a public instance put a reverse proxy with HTTPS in front of port 5029.
How do I update NetSendo?
Back up the database, then in the installation folder run git pull, docker compose pull and docker compose up -d. git pull also brings the new docker-compose.yml and docker/php/docker-entrypoint.sh, which docker compose pull does not update; an installation made with install.sh downloads them from the release instead. Database migrations run when the app container starts, and the other containers wait for them. To stay on a specific release, set NETSENDO_VERSION, for example NETSENDO_VERSION=2.7.0.
Who becomes the administrator of a new installation?
The first person who registers on a fresh installation. After that, registration is closed and the administrator adds further users, so register right after the first start, before the instance is reachable from the internet.
Why did Docker create a .env folder instead of a file?
Docker creates a folder when the file it should mount does not exist. Stop the containers, delete the folder, copy .env.example to .env, fill it in and start again. Always create .env before docker compose up -d.

