Deployment
Target: Ubuntu 24.04 LTS on an OCI VPS. Two viable deployment modes — pick one:
- Mode A (recommended): Docker Compose for the API + nginx.
DaftAlerts.EmailIngestruns as a native binary on the host so Postfix can invoke it directly. - Mode B: systemd unit for the API, native EmailIngest, nginx from the system package repository.
Both modes use the same SQLite file at /var/lib/daftalerts/daftalerts.db.
Prerequisites
apt update
apt install -y postfix mailutils nginx-light certbot python3-certbot-nginx git
# Mode A only:
apt install -y docker.io docker-compose-plugin
One-time host setup
# Clone somewhere stable
cd /opt
git clone https://github.com/guibranco/daftalerts.git
cd daftalerts
# Create data directories
mkdir -p /var/lib/daftalerts /var/log/daftalerts
useradd --system --home-dir /nonexistent --no-create-home --shell /usr/sbin/nologin daftalerts || true
chown -R daftalerts:daftalerts /var/lib/daftalerts /var/log/daftalerts
TLS with Let’s Encrypt
Issue a certificate for the API’s public hostname:
certbot certonly --standalone -d daftalerts.example.com
Certificates land at /etc/letsencrypt/live/daftalerts.example.com/. Auto-renewal is handled by the certbot.timer systemd unit that the package installs.
Mode A: Docker Compose
# Create a .env next to docker-compose.yml
cat >/opt/daftalerts/.env <<'EOF'
DAFTALERTS_API_TOKEN=<generate with: openssl rand -hex 32>
GOOGLE_GEOCODING_API_KEY=<your Google key, optional>
FRONTEND_ORIGIN=https://daftalerts.example.com
EOF
chmod 600 /opt/daftalerts/.env
cd /opt/daftalerts
docker compose build
docker compose up -d
# Verify
curl -s http://127.0.0.1:5080/health
Edit deploy/nginx/daftalerts.conf to set the real server_name and certificate paths. The compose file mounts /etc/letsencrypt read-only into the nginx container.
Publish EmailIngest for Postfix (outside the container)
The ingest binary must run on the host so Postfix can invoke it.
# On a machine with .NET 10 SDK, or in a throwaway docker run
dotnet publish src/DaftAlerts.EmailIngest/DaftAlerts.EmailIngest.csproj \
-c Release -r linux-x64 --self-contained true \
/p:PublishSingleFile=true /p:InvariantGlobalization=true \
-o ./out/ingest
# Copy to the server, then:
sudo ./deploy/install.sh ./out/ingest daft
See deploy/postfix-setup.md for details on what install.sh does.
Mode B: systemd
# On a build host:
dotnet publish src/DaftAlerts.Api/DaftAlerts.Api.csproj -c Release -o ./out/api
# On the server:
install -d -o daftalerts -g daftalerts /opt/daftalerts/api
cp -r ./out/api/* /opt/daftalerts/api/
chown -R daftalerts:daftalerts /opt/daftalerts/api
# Install the .NET 10 runtime on the server
apt install -y aspnetcore-runtime-10.0
# Install env file with secrets
cat >/etc/daftalerts/api.env <<'EOF'
DaftAlerts__Auth__ApiToken=<token>
DaftAlerts__Geocoding__GoogleApiKey=<google-key>
DaftAlerts__Cors__AllowedOrigins__0=https://daftalerts.example.com
DaftAlerts__ConnectionStrings__Default=Data Source=/var/lib/daftalerts/daftalerts.db;Cache=Shared;Foreign Keys=true
DaftAlerts__Database__AutoMigrate=true
EOF
chmod 600 /etc/daftalerts/api.env
# Install the unit
cp deploy/systemd/daftalerts-api.service /etc/systemd/system/
# Edit the unit to uncomment EnvironmentFile=/etc/daftalerts/api.env
systemctl daemon-reload
systemctl enable --now daftalerts-api
systemctl status daftalerts-api
# Install the EmailIngest binary the same way as Mode A
sudo ./deploy/install.sh ./out/ingest daft
Configure nginx manually:
cp deploy/nginx/daftalerts.conf /etc/nginx/sites-available/daftalerts
# Edit server_name and certificate paths
ln -sf /etc/nginx/sites-available/daftalerts /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Backup & restore
The database is a single SQLite file. Back it up hot with the .backup command:
sqlite3 /var/lib/daftalerts/daftalerts.db ".backup /var/backups/daftalerts-$(date +%F).db"
A minimal nightly backup cron:
0 4 * * * /usr/bin/sqlite3 /var/lib/daftalerts/daftalerts.db ".backup /var/backups/daftalerts-$(date +\%F).db" && find /var/backups/daftalerts-*.db -mtime +14 -delete
Monitoring
- API logs:
/var/log/daftalerts/api-*.log(rolling daily, 14 retained). - Ingest logs:
/var/log/daftalerts/ingest-*.log(rolling daily, 30 retained). - Postfix logs:
/var/log/mail.logorjournalctl -u postfix. - Docker logs:
docker compose logs -f api. - Health:
curl -s https://daftalerts.example.com/health # liveness curl -s https://daftalerts.example.com/health/ready # readiness
Updating
cd /opt/daftalerts
git pull
# Mode A:
docker compose build && docker compose up -d
# Mode B:
dotnet publish ... && systemctl restart daftalerts-api
Migrations run automatically on startup when Database:AutoMigrate=true. To run them manually instead, set AutoMigrate=false and run dotnet ef database update on the host against the prod connection string.