Describe the bug
main.initializeStorage() (main.go:105) calls panic(err) unconditionally when
store.Initialize() fails at startup, for any underlying error — including a transient
network/DNS failure to the configured storage.type: postgres backend. Combined with a
restart: unless-stopped/always-style supervisor (Docker, systemd, k8s), this turns a
temporary storage-connectivity blip into a tight crash-loop that never lets Gatus reach the
point where it would actually run and report on the endpoints it's configured to monitor.
This is the same crash signature as #1633 (panic: EOF at main.initializeStorage/
main.go:105, fixed for that specific case by bumping lib/pq in #1644) — but the underlying
issue is broader than any one driver bug: any transient failure to reach the storage backend at
startup is fatal, by design (confirmed in #1395: "Gatus is built with fail fast in mind"). This
report is about a different trigger that isn't fixed by a driver bump.
What do you see?
Setup: storage.type: postgres, storage.path pointing at a Postgres/TimescaleDB container by
Docker Compose service name. When that container is removed from the Docker network (not
merely stopped/restarted — a genuinely different condition than a normal "connection refused"),
Docker's embedded DNS resolver (127.0.0.11:53) returns a malformed/unusual response rather than
NXDOMAIN, which the Go resolver surfaces as server misbehaving:
panic: dial tcp: lookup timescaledb-v2 on 127.0.0.11:53: server misbehaving
goroutine 1 [running]:
main.initializeStorage(0x1c611bc80000)
/app/main.go:105 +0x1766
main.main()
/app/main.go:34 +0xc5
lib/pq propagates this raw dial error out of store.Initialize(), and main.go panics on it
unconditionally. Under restart: unless-stopped, this repeated 206 times over ~19 hours
(224 occurrences of the panic line in the container logs) until the storage container was
restored — Gatus never once reached the point of running its configured checks (including, in
our case, a TCP check on that same host, which would have alerted on exactly this outage if
Gatus had ever gotten past initializeStorage).
Full write-up (self-inflicted circular dependency on our end, but the panic-vs-graceful-degrade
question is the part we think is worth raising upstream):
https://github.com/jagatsingh/trading_infrastructure/blob/main/docs/timescaledb-v2-removal-incident.md
What do you expect to see?
Ideally, store.Initialize() failing at startup should not be an unconditional, un-retried
panic(). A few options that would all help:
- Retry storage-connect with backoff for some bounded window before giving up, rather than
panicking on the first attempt.
- Log the fatal error clearly and
os.Exit(1) instead of panic(), so it's visually obvious
this is a startup-config problem rather than an internal Gatus bug (the current panic/stack
trace reads like an application crash, not "your storage backend is unreachable").
- (Bigger ask, understand if out of scope) an option to fall back to an in-memory store with a
loud warning if the configured persistent store can't be reached at startup, rather than
refusing to start at all — since a monitoring tool arguably has more value running
degraded than not running.
Any of the above would have meant Gatus kept running and alerting throughout the incident above
instead of crash-looping silently for ~19 hours.
Steps to reproduce
docker compose with two services: a Postgres/TimescaleDB container (db) and Gatus
configured with storage.type: postgres, storage.path: postgres://...@db:5432/gatus....
- Start the stack; confirm Gatus is running normally.
docker rm -f db (a full remove, not docker stop) while Gatus keeps running.
- Wait for Gatus's own
restart: unless-stopped policy (or manually restart the Gatus
container).
- Observe
panic: dial tcp: lookup db on 127.0.0.11:53: server misbehaving at
main.initializeStorage/main.go:105, repeating on every restart until db exists again.
Version
v5.36.0
Additional information
- Confirmed via source read at the
v5.36.0 tag that this is not a bug in the per-check TCP
code path (client.CanCreateNetworkConnection already returns (false, nil) gracefully on any
dial error, DNS failures included) — the panic is specifically in the storage-initialization
path at startup.
- Our fix on our end is to stop pointing Gatus's own storage at a container one of its own
checks monitors (removes the circular dependency entirely) — filing this because the
unconditional-panic-on-storage-init-failure behavior itself seems like it could bite anyone
whose storage backend has any transient startup-time connectivity hiccup, not just our specific
circular-dependency case.
Describe the bug
main.initializeStorage()(main.go:105) callspanic(err)unconditionally whenstore.Initialize()fails at startup, for any underlying error — including a transientnetwork/DNS failure to the configured
storage.type: postgresbackend. Combined with arestart: unless-stopped/always-style supervisor (Docker, systemd, k8s), this turns atemporary storage-connectivity blip into a tight crash-loop that never lets Gatus reach the
point where it would actually run and report on the endpoints it's configured to monitor.
This is the same crash signature as #1633 (
panic: EOFatmain.initializeStorage/main.go:105, fixed for that specific case by bumpinglib/pqin #1644) — but the underlyingissue is broader than any one driver bug: any transient failure to reach the storage backend at
startup is fatal, by design (confirmed in #1395: "Gatus is built with fail fast in mind"). This
report is about a different trigger that isn't fixed by a driver bump.
What do you see?
Setup:
storage.type: postgres,storage.pathpointing at a Postgres/TimescaleDB container byDocker Compose service name. When that container is removed from the Docker network (not
merely stopped/restarted — a genuinely different condition than a normal "connection refused"),
Docker's embedded DNS resolver (
127.0.0.11:53) returns a malformed/unusual response rather thanNXDOMAIN, which the Go resolver surfaces as
server misbehaving:lib/pqpropagates this raw dial error out ofstore.Initialize(), andmain.gopanics on itunconditionally. Under
restart: unless-stopped, this repeated 206 times over ~19 hours(224 occurrences of the panic line in the container logs) until the storage container was
restored — Gatus never once reached the point of running its configured checks (including, in
our case, a TCP check on that same host, which would have alerted on exactly this outage if
Gatus had ever gotten past
initializeStorage).Full write-up (self-inflicted circular dependency on our end, but the panic-vs-graceful-degrade
question is the part we think is worth raising upstream):
https://github.com/jagatsingh/trading_infrastructure/blob/main/docs/timescaledb-v2-removal-incident.md
What do you expect to see?
Ideally,
store.Initialize()failing at startup should not be an unconditional, un-retriedpanic(). A few options that would all help:panicking on the first attempt.
os.Exit(1)instead ofpanic(), so it's visually obviousthis is a startup-config problem rather than an internal Gatus bug (the current panic/stack
trace reads like an application crash, not "your storage backend is unreachable").
loud warning if the configured persistent store can't be reached at startup, rather than
refusing to start at all — since a monitoring tool arguably has more value running
degraded than not running.
Any of the above would have meant Gatus kept running and alerting throughout the incident above
instead of crash-looping silently for ~19 hours.
Steps to reproduce
docker composewith two services: a Postgres/TimescaleDB container (db) and Gatusconfigured with
storage.type: postgres,storage.path: postgres://...@db:5432/gatus....docker rm -f db(a full remove, notdocker stop) while Gatus keeps running.restart: unless-stoppedpolicy (or manually restart the Gatuscontainer).
panic: dial tcp: lookup db on 127.0.0.11:53: server misbehavingatmain.initializeStorage/main.go:105, repeating on every restart untildbexists again.Version
v5.36.0
Additional information
v5.36.0tag that this is not a bug in the per-check TCPcode path (
client.CanCreateNetworkConnectionalready returns(false, nil)gracefully on anydial error, DNS failures included) — the panic is specifically in the storage-initialization
path at startup.
checks monitors (removes the circular dependency entirely) — filing this because the
unconditional-panic-on-storage-init-failure behavior itself seems like it could bite anyone
whose storage backend has any transient startup-time connectivity hiccup, not just our specific
circular-dependency case.