Last updated 2026-05-28

Docker

The reference deployment shape is the published multi-arch container image at ghcr.io/elloloop/notify. It is a FROM scratch binary plus CA certificates — no shell, no package manager, ~10 MB.

When you'd use this

This is the recommended path for every environment except library mode. Local dev, staging, production: same image, different env vars. If you want to embed notify in another Go binary, see library usage in the README.

Pull and run

docker run
docker pull ghcr.io/elloloop/notify:latest
docker run --rm \
-p 8080:8080 -p 8081:8081 -p 9090:9090 \
-e NOTIFY_STORE_DRIVER=memory \
-e NOTIFY_AUTH_JWT_SECRET=$(openssl rand -hex 32) \
-e NOTIFY_INTERNAL_TOKEN=$(openssl rand -hex 32) \
-e NOTIFY_EMAIL_PROVIDER=none \
ghcr.io/elloloop/notify:latest

Pin to a specific tag in CI and production:

docker pull ghcr.io/elloloop/notify:0.1.0

Ports

PortListenerAudience
8080NotificationClientServiceBrowser / mobile recipients (Connect HTTP/2)
8081NotificationInternalServiceBackend producers (gRPC / Connect)
9090/healthz and /metricsOrchestrators / Prometheus

Image layout

The Dockerfile builds a FROM --platform=$BUILDPLATFORM golang:1.26.3-alpine3.23 stage that produces a static binary, then COPYs the binary and CA bundle into FROM scratch. The final image runs as UID 65532 (non-root):

Dockerfile
FROM scratch AS server
COPY --from=builder /bin/notifyd /bin/notifyd
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 8080 8081 9090
USER 65532:65532
ENTRYPOINT ["/bin/notifyd"]

Healthcheck

The container has no shell — you can't HEALTHCHECK CMD curl … against itself. Probe from the orchestrator side instead:

docker-compose snippet (probe from a sidecar)
# docker-compose
healthcheck:
test: ["CMD-SHELL", "wget -q -O- http://localhost:9090/healthz | grep -q ok"]
interval: 10s
timeout: 3s
retries: 5

For Kubernetes use HTTP probes — see Kubernetes.

docker-compose example

compose.yaml
services:
notify:
image: ghcr.io/elloloop/notify:0.1.0
ports:
- "8080:8080" # client
- "8081:8081" # internal
- "9090:9090" # metrics
environment:
NOTIFY_STORE_DRIVER: memory
NOTIFY_AUTH_JWT_SECRET: ${NOTIFY_AUTH_JWT_SECRET}
NOTIFY_INTERNAL_TOKEN: ${NOTIFY_INTERNAL_TOKEN}
NOTIFY_LOG_LEVEL: info
# Channel providers
NOTIFY_EMAIL_PROVIDER: emailservice
NOTIFY_EMAIL_SERVICE_ADDRESS: emailservice:50053
NOTIFY_EMAIL_FROM: noreply@example.com
NOTIFY_SMS_PROVIDER: twilio
NOTIFY_SMS_ACCOUNT_SID: ${TWILIO_ACCOUNT_SID}
NOTIFY_SMS_AUTH_TOKEN: ${TWILIO_AUTH_TOKEN}
NOTIFY_SMS_FROM: "+15555550000"

Image verification

Every published image is signed via cosign keyless OIDC and scanned by Trivy with a HIGH/CRITICAL gate. Verify the signature on pull:

cosign verify ghcr.io/elloloop/notify:0.1.0 \
--certificate-identity-regexp 'https://github\.com/elloloop/notify/' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

Building locally

git clone https://github.com/elloloop/notify
cd notify
docker build -t notify:dev .
docker run --rm \
-p 8080:8080 -p 8081:8081 -p 9090:9090 \
-e NOTIFY_AUTH_DEV_MODE=true \
-e NOTIFY_STORE_DRIVER=memory \
notify:dev

Related