Manual deployment
Deploy Bulwark directly on a Linux server without Docker.
Prerequisites
- Node.js 20 or later
- A process manager, PM2 or systemd
- A reverse proxy: Nginx, Caddy, or whatever you already run
Steps
1. Clone and build
git clone https://github.com/bulwarkmail/webmail.git
cd webmail
npm install
npm run build
The build produces a standalone output in .next/standalone that includes all dependencies.
2. Configure the environment (optional)
The web setup wizard handles JMAP, OAuth, branding, and the admin password on first launch, so .env.local is only worth writing when you want env-driven, immutable configuration. To pre-seed it:
cp .env.example .env.local
Set your JMAP endpoint and any other configuration:
JMAP_SERVER_URL=https://mail.example.com
APP_NAME=Bulwark
Setting JMAP_SERVER_URL in the environment skips the wizard.
3. Run with PM2
npm install -g pm2
pm2 start npm --name bulwark -- start
pm2 save
pm2 startup
4. Run with systemd
Create /etc/systemd/system/bulwark.service:
[Unit]
Description=Bulwark Webmail
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/bulwark
ExecStart=/usr/bin/node .next/standalone/server.js
Restart=on-failure
Environment=NODE_ENV=production
Environment=PORT=3000
Environment=HOSTNAME=0.0.0.0
# Admin dirs - point at a writable, service-owned location
Environment=ADMIN_CONFIG_DIR=/var/lib/bulwark/admin
Environment=ADMIN_STATE_DIR=/var/lib/bulwark/admin-state
Environment=TELEMETRY_DATA_DIR=/var/lib/bulwark/telemetry
EnvironmentFile=/opt/bulwark/.env.local
[Install]
WantedBy=multi-user.target
Make sure the www-data user (or whichever user runs the service) owns the directories listed in ADMIN_CONFIG_DIR, ADMIN_STATE_DIR, and TELEMETRY_DATA_DIR so the setup wizard can write to them.
Enable and start:
sudo systemctl enable bulwark
sudo systemctl start bulwark
Updating
cd /opt/bulwark
git pull origin main
npm install
npm run build
pm2 restart bulwark
# or: sudo systemctl restart bulwark
Updating covers release channels and which directories have to persist.