ghcr.io/useplunk/plunk) that bundles every application service. PM2 manages the processes inside the container, and nginx routes incoming requests to the correct service based on the Host header.
The image
v1.2.3) and latest always points to the most recent stable release.
The image contains:
- API server — Express.js HTTP API on port 8080
- Worker process — BullMQ worker that processes email, campaign, and workflow queues
- Web dashboard — Next.js app on port 3000
- Landing page — Next.js app on port 4000
- Documentation site — Next.js app on port 1000
- SMTP relay — Optional SMTP server on ports 465 and 587
- nginx — Reverse proxy on port 80 that routes by
Hostheader to the services above
docker-compose.yml breakdown
The recommended way to run Plunk is with Docker Compose. Here is the fullplunk service definition from the official Compose file:
plunk container depends on all four infrastructure services and waits for their health checks before starting.
Runtime environment injection
The Next.js apps (dashboard, landing, wiki) are built at image creation time with placeholder URLs baked into the JavaScript bundles. When the container starts, the entrypoint script (docker-entrypoint-nginx.sh) replaces those placeholder URLs with the real values derived from your *_DOMAIN and USE_HTTPS environment variables.
This means the same Docker image can be deployed to any domain without rebuilding. You only need to change the environment variables.
You do not need to set
API_URI, DASHBOARD_URI, or any NEXT_PUBLIC_* variables. The entrypoint script constructs them automatically from API_DOMAIN, DASHBOARD_DOMAIN, and USE_HTTPS.Subdomain routing
nginx inside the container routes requests byHost header:
| Host header | Internal port | Service |
|---|---|---|
$API_DOMAIN | 8080 | API server |
$DASHBOARD_DOMAIN | 3000 | Web dashboard |
$LANDING_DOMAIN | 4000 | Landing page |
$WIKI_DOMAIN | 1000 | Documentation |
plunk container.
Database migrations
Prisma migrations run automatically when the container starts, before any application process is launched. If migrations fail, the container will exit. Check the container logs if your instance fails to start:Running individual services
Set theSERVICE environment variable to run only specific processes instead of the full stack:
Storage (Minio)
The bundled Minio container provides S3-compatible file storage for email template assets and uploads. It is pre-configured to work with Plunk out of the box — no extra setup is needed. Minio exposes two ports:| Port | Purpose |
|---|---|
9000 | S3 API (used by Plunk) |
9001 | Minio console UI |
S3_* environment variables to point at your bucket and remove or disable the Minio service from the Compose file.
Notifications (ntfy)
The bundled ntfy container receives internal system notifications from Plunk — for example, when a project is suspended due to high bounce rates. The ntfy web UI is available on the port configured byNTFY_PORT (default: 8080). Subscribe to the plunk-notifications topic to receive notifications.
To use an external ntfy.sh server or your own instance, set NTFY_URL to your topic URL.
SMTP TLS certificates
The optional SMTP relay (ports 465 and 587) requires TLS certificates in production. Provide them by mounting files into the container.- Traefik acme.json
- PEM files
If you use Traefik (as in Dokploy or Coolify), mount its Plunk reads the certificate for
acme.json file and set SMTP_DOMAIN so Plunk can extract the correct certificate:SMTP_DOMAIN from acme.json automatically on startup.If no certificates are mounted, the SMTP server starts without TLS. This is acceptable for local testing but should not be used in production.
Health checks and logs
Theplunk container has a built-in health check that polls nginx on port 80:
Upgrading
Building from source
If you want to build a custom image from the repository:image: field in your docker-compose.yml to plunk:custom.