Production Deployment
Deploy a self-hosted Emberly instance to a Linux server with Bun and systemd.
This guide covers deploying Emberly to a Linux server directly with Bun, managed by systemd — the approach the project's own README documents. There is no official Docker image, Docker Compose file, or Nixpacks config shipped with the project today; if you containerize it yourself, you'll need to write that Dockerfile from scratch using the steps below as a guide.
Prerequisites
- A Linux server (Ubuntu 22.04 LTS recommended) with at least 2 GB RAM
- A domain name pointing to your server's IP (
Arecord) - Bun and Node.js 18+
- PostgreSQL 14+ and Redis 6+ (either on the same server or reachable over the network)
- An S3-compatible object storage bucket (AWS S3, Cloudflare R2, Vultr Object Storage, or self-hosted MinIO)
Step 1: Server Preparation
Step 2: Clone and Configure
Edit .env with your database/Redis connection strings, NEXTAUTH_URL/NEXT_PUBLIC_BASE_URL (your domain), a generated NEXTAUTH_SECRET, and OAuth credentials. See Environment Variables for the full reference — storage and email are configured later via the Admin Panel, not .env.
Step 3: Database Setup
Step 4: Build
Step 5: Run as a systemd Service
Create /etc/systemd/system/emberly.service:
The background event worker (BullMQ, used for async jobs like VirusTotal scanning and OCR) must run as a separate process in production — it only auto-starts in-process during local development. Create /etc/systemd/system/emberly-worker.service:
The app listens on port 3000 by default. Open http://YOUR_SERVER_IP:3000 and complete the Setup Wizard to create your admin account — see First-Time Setup for a step-by-step walkthrough.
Step 6: Reverse Proxy with HTTPS
Use Nginx or Caddy to terminate TLS and proxy to port 3000. Caddy is simpler because it provisions Let's Encrypt certificates automatically.
Caddy (Recommended)
Install Caddy, then create /etc/caddy/Caddyfile:
Nginx + Certbot
Create /etc/nginx/sites-available/emberly:
Step 7: Configure Storage
You already chose local or S3-compatible storage during the Setup Wizard's Storage step (see First-Time Setup). To switch providers later, or fill in the public URL and other advanced fields, go to Admin Panel → Settings → Integrations → Storage:
| Field | Description |
|---|---|
| Provider | AWS S3, Cloudflare R2, Vultr, Linode, MinIO, etc. |
| Bucket name | The bucket to store files in |
| Region | Your bucket's region |
| Endpoint | Custom endpoint (leave blank for AWS) |
| Access key ID | Storage access key |
| Secret access key | Storage secret key |
| Public files URL | The CDN/public URL prefix for served files |
The public files URL is what gets prepended to file paths in share links. For Cloudflare R2, this is your R2 public bucket URL or a custom domain. For AWS, it is the bucket's public URL or a CloudFront distribution.
Step 8: Configure Email
In Admin Panel → Settings → Integrations → Email, configure Resend or SMTP:
Resend:
- Get an API key from resend.com
- Set the from address (must be on a verified domain)
SMTP:
- Use any SMTP provider (Postmark, Mailgun, Gmail, etc.)
- Set host, port, username, and password
Email is used for verification emails, expiration reminders, and collaborator invites.
Step 9: Optional Integrations
Configure these in the Admin Panel as needed:
| Integration | Purpose |
|---|---|
| VirusTotal | File malware scanning (skipped for images/video/audio) |
| Sentry | Error tracking |
Updating
Always run migrations before restarting the app on an update.
Health Checks
GET /api/health reports the status of your instance — database, Redis, storage (a real write/delete round-trip, cached for 30 seconds), and the event queue. No authentication required. Point an uptime monitor or your reverse proxy's health check at it.
Response (200 or 503):
status is ok, degraded (a non-critical check is failing), or down (the database is unreachable — responds with HTTP 503 in that case).
Backups
Back up the PostgreSQL database regularly:
Also back up your S3 bucket. Use your provider's snapshot or bucket replication features.
Troubleshooting
| Problem | Check |
|---|---|
| App won't start | sudo journalctl -u emberly -f — look for missing env vars or DB connection errors |
| Database connection error | Verify DATABASE_URL and that PostgreSQL is running and reachable |
| Background jobs not processing (OCR, VirusTotal scans) | Confirm emberly-worker.service is running — it's a separate process, not automatic in production |
| Uploads fail | Check storage configuration in Admin Panel; verify bucket permissions and public URL |
| Email not sending | Verify Resend API key or SMTP credentials in Admin Panel |