Add Dockerfile, Compose stacks, and /healthz endpoint
Dockerfile is multi-stage:
- build: golang:1.25-bookworm, CGO_ENABLED=0 (modernc.org/sqlite
is pure-Go) + -trimpath + -ldflags "-s -w" so the resulting
binary is small and reproducible-ish.
- runtime: gcr.io/distroless/static:nonroot, ~2 MB. Runs as uid
65532. /data and /var/lib/librenotes are declared volumes so
per-tenant notes and the SQLite database survive container
restarts.
healthcheck subcommand: distroless static has no shell or
wget/curl, so /healthz is reachable but no client to call it. A
new "librenotes healthcheck" subcommand uses net/http to GET
$LIBRENOTES_HEALTHCHECK_URL (default 127.0.0.1:8080/healthz) and
exits non-zero on failure. Both compose files invoke it from the
HEALTHCHECK directive.
httpapi adds a tiny GET /healthz that returns {"status":"ok"}
(no DB ping yet — added when readiness probes need it).
docker-compose.yml: dev stack on :8080 with named volumes and a
LogMailer; everything via env vars, JWT secret defaulted to a
dev value.
docker-compose.prod.yml: layered overrides — pulls a registry
image, expects LIBRENOTES_* env, sets memory limits and JSON-
file log rotation.
Closes #25.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// runHealthcheck pings /healthz on the local server and exits 0
|
||||
// if the response is 2xx. Useful as a Docker HEALTHCHECK on
|
||||
// distroless images that don't ship curl/wget.
|
||||
//
|
||||
// Defaults to http://127.0.0.1:8080/healthz; override with
|
||||
// LIBRENOTES_HEALTHCHECK_URL.
|
||||
func runHealthcheck(_ []string) error {
|
||||
url := os.Getenv("LIBRENOTES_HEALTHCHECK_URL")
|
||||
if url == "" {
|
||||
url = "http://127.0.0.1:8080/healthz"
|
||||
}
|
||||
client := http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+14
-5
@@ -9,12 +9,21 @@ import (
|
||||
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
if len(args) > 0 && args[0] == "serve" {
|
||||
if err := runServe(args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "serve:", err)
|
||||
os.Exit(1)
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "serve":
|
||||
if err := runServe(args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "serve:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
case "healthcheck":
|
||||
if err := runHealthcheck(args[1:]); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "healthcheck:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
notesium.Run()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user