Dokploy / Traefik
docker-compose.dokploy.yml is a self-contained compose file: every service extends its definition in docker-compose.yml, and the file adds the Traefik network and the router labels for TLS. Nothing publishes a host port: the loopback binds live in docker-compose.override.yml, which Compose only loads implicitly — a Dokploy deployment never sees them (and port 3000 is Dokploy's own UI). The worker takes no inbound traffic and stays on the internal network.
Configure
# .env
API_DOMAIN=push.example.com
DASHBOARD_DOMAIN=push-admin.example.com
DOCS_DOMAIN=docs.example.com
TRAEFIK_NETWORK=dokploy-network # or whatever your Traefik network is called
TRAEFIK_CERT_RESOLVER=letsencrypt
API_PUBLIC_URL=https://push.example.com
CORS_ORIGINS=https://www.example.com,https://app.example.com
Run
docker compose -f docker-compose.dokploy.yml up -d
In Dokploy
- Create a Compose application and point it at your fork of the repository.
- Set the compose path to
docker-compose.dokploy.yml. Nothing else: no additional file, no custom command. (If onlydocker-compose.ymlruns, the containers come up healthy but carry no router labels, and Traefik answers404 page not foundon both domains.) - Paste your
.envinto the Environment tab.ENCRYPTION_KEY,HMAC_SECRETand the dashboard secrets (ADMIN_API_TOKEN,SESSION_SECRET,ADMIN_PASSWORD_HASH) must be there; Compose refuses to start otherwise. - Deploy. Traefik picks up the labels and issues certificates for the three domains.
What the labels do
traefik.http.routers.opennotification-api.rule: Host(`${API_DOMAIN}`)
traefik.http.routers.opennotification-api.entrypoints: websecure
traefik.http.routers.opennotification-api.tls.certresolver: ${TRAEFIK_CERT_RESOLVER}
traefik.http.services.opennotification-api.loadbalancer.server.port: "3000"
A small middleware strips X-Powered-By. Push SDKs call this endpoint from phones and browsers, so keep proxy timeouts short: a stuck upstream should be shed, not queued.
Scaling behind Traefik
docker compose -f docker-compose.dokploy.yml up -d --scale worker=3
Queues are shared in Redis, so workers simply split the load. The API is stateless and can be scaled the same way; Traefik round-robins across replicas.
Dashboard
The file routes the dashboard on DASHBOARD_DOMAIN with the same TLS resolver; it reaches the API over the internal network as http://api:3000. It is an admin surface: if you would rather not expose it at all, drop the dashboard labels from the file and reach 127.0.0.1:3001 over a VPN or SSH tunnel instead.
Documentation site
The docs are part of the same stack: a docs service (static files behind nginx) routed on DOCS_DOMAIN. Set it in the Environment tab next to the other two domains; nothing else to deploy.