Add a Dockerfile so the sync server is one command to deploy

Self-hosting meant building the binary by hand and wiring Postgres up
yourself. The image is a static binary on distroless with a built-in
healthcheck flag, and docker-compose brings up Postgres alongside it.

The image itself is unbuilt here: no container runtime is available in
this environment, so only the static Linux build and the healthcheck
flag have been exercised.

💘 Generated with Crush

Assisted-by: Crush:kimi-k3
This commit is contained in:
2026-10-08 10:23:54 +08:00
parent f3e6fc8fa6
commit cbdf5538aa
7 changed files with 132 additions and 3 deletions
+31
View File
@@ -27,6 +27,7 @@ import (
"os"
"os/signal"
"regexp"
"strings"
"syscall"
"time"
@@ -48,6 +49,13 @@ type Server struct {
}
func main() {
// -healthcheck pings the local /healthz and exits non-zero on failure. It
// exists so the container image can run on a shell-less base (distroless),
// where there is no wget or curl for a HEALTHCHECK to use.
if len(os.Args) > 1 && os.Args[1] == "-healthcheck" {
os.Exit(healthcheck())
}
dsn := os.Getenv("PROMPT_SYNC_DATABASE_URL")
if dsn == "" {
dsn = "postgres://localhost:5432/prompt_sync?sslmode=disable"
@@ -103,6 +111,29 @@ func main() {
}
}
// healthcheck returns 0 when the local /healthz answers 200.
func healthcheck() int {
addr := os.Getenv("PROMPT_SYNC_ADDR")
if addr == "" {
addr = ":8080"
}
if strings.HasPrefix(addr, ":") {
addr = "127.0.0.1" + addr
}
client := &http.Client{Timeout: 3 * time.Second}
response, err := client.Get("http://" + addr + "/healthz")
if err != nil {
log.Printf("healthcheck: %v", err)
return 1
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
log.Printf("healthcheck: unexpected status %d", response.StatusCode)
return 1
}
return 0
}
// withLogging keeps a one-line trail of method, path, status and duration so a
// self-hosted deployment can be debugged from journalctl or docker logs.
func withLogging(next http.Handler) http.Handler {