Deployment and IaC¶
How to run the HTTP API server (hibp serve) as a service — from a bare docker run up to infrastructure-as-code. The repository ships Docker + Compose; the Kubernetes/systemd/Terraform recipes below are equivalent replacements you can copy into your own IaC.
The contract (all deployment methods)¶
| Aspect | Value |
|---|---|
| Image | ghcr.io/marco-montesines/haveibeenpwned (multi-arch: linux/amd64, linux/arm64) |
| Tags | :latest, :X.Y.Z, :X.Y, :sha-<commit> — see Releases and Versioning |
| Listen port | 8080 |
| Health check | GET /healthz → {"status":"ok"} |
| Configuration | HIBP_API_KEY env var (optional — required only for the account endpoints) |
| State | None — the server is stateless; scale horizontally at will |
| Shutdown | Graceful on SIGTERM/SIGINT (10s drain) |
Treat HIBP_API_KEY as a secret everywhere: secret store, not plain env in committed files.
Docker (plain)¶
docker run -d --name hibp \
-p 8080:8080 \
-e HIBP_API_KEY=... \
--restart unless-stopped \
ghcr.io/marco-montesines/haveibeenpwned:1.0
The examples use the :1.0 minor tag, which follows patch releases automatically. For exact reproducibility pin the full :X.Y.Z of the current release; use :latest only for experiments.
Docker Compose¶
The repo's docker-compose.yml starts the API plus the FrankenPHP demo. A minimal production-style service for your own stack:
services:
hibp-api:
image: ghcr.io/marco-montesines/haveibeenpwned:1.0
restart: unless-stopped
environment:
HIBP_API_KEY: ${HIBP_API_KEY}
# No `ports:` — reach it via the Compose network as http://hibp-api:8080
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:8080/healthz"]
interval: 30s
timeout: 3s
retries: 3
Other services on the same Compose network call http://hibp-api:8080/v1/... — no host port exposure needed.
Kubernetes¶
Equivalent manifests (Deployment + Service + Secret). The service is stateless, so replicas are safe — but remember all replicas share one HIBP API key and therefore one upstream rate limit.
apiVersion: v1
kind: Secret
metadata:
name: hibp-api-key
stringData:
HIBP_API_KEY: "<your key>" # in real IaC: sealed-secrets / external-secrets / SOPS
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hibp-api
spec:
replicas: 2
selector:
matchLabels: { app: hibp-api }
template:
metadata:
labels: { app: hibp-api }
spec:
containers:
- name: hibp-api
image: ghcr.io/marco-montesines/haveibeenpwned:1.0
ports:
- containerPort: 8080
envFrom:
- secretRef: { name: hibp-api-key }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { memory: 128Mi }
---
apiVersion: v1
kind: Service
metadata:
name: hibp-api
spec:
selector: { app: hibp-api }
ports:
- port: 80
targetPort: 8080
Keep the Service ClusterIP (internal). If you must expose it, do so behind an authenticating ingress/gateway — the server itself has no auth or TLS (see the deployment note in HTTP API Reference). A Helm chart / Kustomize base is under consideration — until then, these manifests are the template.
Terraform (example)¶
Any Terraform provider that runs containers works — the image is a plain OCI image on GHCR (public, no pull auth). Docker provider example:
resource "docker_image" "hibp" {
name = "ghcr.io/marco-montesines/haveibeenpwned:1.0"
}
resource "docker_container" "hibp_api" {
name = "hibp-api"
image = docker_image.hibp.image_id
restart = "unless-stopped"
env = ["HIBP_API_KEY=${var.hibp_api_key}"] # var marked sensitive = true
ports {
internal = 8080
external = 8080
}
}
The same contract (image/port/env/healthcheck) maps directly onto ECS task definitions, Cloud Run services, Nomad jobs, etc.
systemd (no containers)¶
# /etc/systemd/system/hibp-api.service
[Unit]
Description=HIBP HTTP JSON API
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/hibp serve -addr 127.0.0.1:8080
EnvironmentFile=/etc/hibp-api.env # HIBP_API_KEY=... (chmod 600)
DynamicUser=yes
NoNewPrivileges=yes
ProtectSystem=strict
Restart=on-failure
[Install]
WantedBy=multi-user.target
Install the binary with go install github.com/marco-montesines/haveibeenpwned/cmd/hibp@latest (or build with go build ./cmd/hibp), then systemctl enable --now hibp-api.
Building images yourself¶
docker build -t hibp . # the API server image (what GHCR hosts)
docker build -f frankenphp/Dockerfile -t hibp-frankenphp . # FrankenPHP demo (slow: compiles PHP + extension)
Upgrades¶
Bump the image tag (new versions appear on releases), roll the deployment. The server is stateless with graceful shutdown, so rolling updates are zero-downtime as long as you run ≥2 replicas behind a load balancer or Service.