Skip to content

🐛 [Bug]: Prefork children exit immediately in Docker containers (PID 1 detection) #4132

@meruiden

Description

@meruiden

Bug Description

watchMaster() in prefork.go kills child processes when os.Getppid() == 1, assuming the parent died and init adopted them. However, in Docker containers the application commonly is PID 1, so children immediately see their parent PID as 1 and call os.Exit(1) after the first 500ms tick.

https://github.com/gofiber/fiber/blob/main/prefork.go#L170-L174

for range time.NewTicker(watchInterval).C {
    if os.Getppid() == 1 {
        os.Exit(1)
    }
}

How to Reproduce

FROM golang:1.24-alpine AS builder
WORKDIR /src
COPY . .
RUN go build -o /app .

FROM alpine:3.21
COPY --from=builder /app /app
ENTRYPOINT ["/app"]
package main

import (
    "log"
    "github.com/gofiber/fiber/v3"
)

func main() {
    app := fiber.New()
    app.Get("/", func(c fiber.Ctx) error {
        return c.SendString("OK")
    })
    log.Fatal(app.Listen(":8000", fiber.ListenConfig{
        EnablePrefork: true,
    }))
}
docker build -t test . && docker run --rm -p 8000:8000 test

Children spawn, then all exit with exit status 1 after ~500ms.

Expected Behavior

Prefork should work in Docker containers without requiring an external init process.

Workaround

Use tini as PID 1 so the app doesn't run as PID 1:

RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/app"]

Suggested Fix

Instead of checking os.Getppid() == 1, store the actual parent PID at startup and compare against that:

func watchMaster() {
    ppid := os.Getppid()
    for range time.NewTicker(watchInterval).C {
        if os.Getppid() != ppid {
            os.Exit(1)
        }
    }
}

This correctly detects parent death (PID changes when reparented to init) regardless of the original parent's PID.

Fiber Version

v3.1.0

Code Snippet (optional)

See above.

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have searched for existing issues that describe my problem before opening this one.
  • I understand that an issue that doesn't follow these guidelines may be closed without explanation.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions