Skip to content

feat: add distroless Docker image with built-in healthcheck#6902

Merged
vyavdoshenko merged 7 commits intomainfrom
bobik/distroless_container
Mar 28, 2026
Merged

feat: add distroless Docker image with built-in healthcheck#6902
vyavdoshenko merged 7 commits intomainfrom
bobik/distroless_container

Conversation

@vyavdoshenko
Copy link
Copy Markdown
Contributor

@vyavdoshenko vyavdoshenko commented Mar 16, 2026

Adds Dockerfile.distroless-dev and Dockerfile.distroless-prod for a minimal shell-less runtime image based on gcr.io/distroless/cc-debian12, and a /healthz HTTP(s) endpoint to Dragonfly for container health checks.

Health checking uses microcheck - two tiny static binaries (~75 KB each) copied into the image:

  • httpcheck — default HEALTHCHECK for plain HTTP deployments
  • httpscheck — available for TLS deployments (https://localhost:6379/healthz)

The prod image (Dockerfile.distroless-prod) is ready but requires a release that
includes the /healthz endpoint. Local strip experiment: ~70 MB release image.

@vyavdoshenko vyavdoshenko self-assigned this Mar 16, 2026
Copilot AI review requested due to automatic review settings March 16, 2026 17:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for a distroless Docker runtime image by introducing a shell-less healthcheck binary and a lightweight /healthz HTTP endpoint, plus CI to build/push the new image.

Changes:

  • Add tools/packaging/Dockerfile.distroless-dev and a GitHub Actions workflow to build/push a distroless image.
  • Add /healthz HTTP handler (and allow unauthenticated access like /metrics) plus a standalone dragonfly-healthcheck client binary.
  • Add a Linux-only --user flag to drop privileges after binding ports and (attempt to) chown the data directory.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tools/packaging/Dockerfile.distroless-dev New distroless image build (copies dragonfly + dragonfly-healthcheck, adds Docker HEALTHCHECK).
tools/docker/fetch_release.sh Minor output formatting tweak.
src/server/main_service.cc Adds /healthz handler and auth bypass for it.
src/server/dfly_main.cc Adds --user privilege-drop implementation and call site before service.Init().
src/server/dfly_healthcheck.cc New minimal HTTP healthcheck helper binary for distroless containers.
src/server/CMakeLists.txt Adds dragonfly-healthcheck target.
Makefile Ensures dragonfly-healthcheck is built/packaged in release artifacts.
.github/workflows/docker-distroless.yml New scheduled/manual workflow to build/test/push distroless image and publish multi-arch manifest.

Comment thread src/server/dfly_main.cc
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/CMakeLists.txt Outdated
Comment thread src/server/dfly_main.cc
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Mar 16, 2026

🤖 Augment PR Summary

Summary: Adds a distroless-based Docker image variant and a built-in healthcheck mechanism suitable for shell-less containers.

Changes:

  • Adds a scheduled/manual GitHub Actions workflow to build, test, and publish multi-arch distroless dev images to GHCR.
  • Introduces tools/packaging/Dockerfile.distroless-dev using gcr.io/distroless/cc-debian12 as the runtime and copying required system libraries (e.g. libz).
  • Adds a standalone dragonfly-healthcheck binary (no dragonfly_lib dependency) intended for Docker HEALTHCHECK CMD.
  • Exposes a new HTTP /healthz endpoint (and exempts it from HTTP basic auth like /metrics) returning 200 only when the server is GlobalState::ACTIVE.
  • Extends build/release packaging (Makefile + CMake) to compile and ship the healthcheck helper.
  • Adds a Linux-only --user flag to chown the data directory and drop process privileges after binding ports.

Technical Notes: The healthcheck uses an HTTP GET against the main port (via primary_port_http_enabled) and the distroless image wires it into Docker’s healthcheck mechanism.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread .github/workflows/docker-distroless.yml Outdated
Comment thread src/server/CMakeLists.txt Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_main.cc Outdated
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from a227553 to 410f7fd Compare March 16, 2026 20:00
Copilot AI review requested due to automatic review settings March 16, 2026 20:24
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from 410f7fd to 43e7b8e Compare March 16, 2026 20:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for producing a minimal distroless Dragonfly Docker image by bundling a standalone dragonfly-healthcheck binary and exposing a lightweight /healthz HTTP endpoint.

Changes:

  • Add /healthz HTTP handler and allow unauthenticated access to it (similar to /metrics).
  • Introduce a new dragonfly-healthcheck executable and wire it into the build/package flow.
  • Add a distroless Dockerfile and a GitHub Actions workflow to build/push a multi-arch distroless image.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tools/packaging/Dockerfile.distroless-dev New distroless image build stage and runtime image with binary healthcheck.
tools/docker/fetch_release.sh Minor script output formatting change.
src/server/main_service.cc Adds /healthz endpoint and auth bypass for it.
src/server/dfly_main.cc Adds --user flag and privilege drop logic on Linux.
src/server/dfly_healthcheck.cc New standalone healthcheck binary (HTTP GET /healthz).
src/server/CMakeLists.txt Adds dragonfly-healthcheck build target.
Makefile Builds/packages the new healthcheck binary.
.github/workflows/docker-distroless.yml New scheduled/manual workflow to build and publish distroless images (multi-arch).

Comment thread src/server/CMakeLists.txt Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_main.cc Outdated
Comment thread tools/packaging/Dockerfile.distroless-dev Outdated
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from 43e7b8e to 2d52e88 Compare March 16, 2026 20:52
Copilot AI review requested due to automatic review settings March 16, 2026 20:59
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from 2d52e88 to 3b3a716 Compare March 16, 2026 20:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for running Dragonfly in a minimal distroless container by introducing a standalone HTTP-based healthcheck binary, wiring a /healthz endpoint into the existing HTTP console handlers, and adding CI to build/publish the new image.

Changes:

  • Add a distroless Dockerfile that builds dragonfly and a new dragonfly-healthcheck binary and configures Docker HEALTHCHECK.
  • Add /healthz HTTP endpoint and exempt it from HTTP auth checks (similar to /metrics).
  • Add a GitHub Actions workflow to build/push a multi-arch distroless image on a schedule.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tools/packaging/Dockerfile.distroless-dev New distroless runtime image build with bundled healthcheck binary and healthcheck config.
tools/docker/fetch_release.sh Minor script formatting fix.
src/server/main_service.cc Adds /healthz handler and exempts it from HTTP auth.
src/server/dfly_main.cc Adds --user flag support to drop privileges after binding ports.
src/server/dfly_healthcheck.cc New minimal standalone healthcheck client binary (no dragonfly_lib).
src/server/CMakeLists.txt Builds the new dragonfly-healthcheck executable.
Makefile Builds/packages dragonfly-healthcheck alongside the main release artifacts.
.github/workflows/docker-distroless.yml New scheduled multi-arch distroless image build/publish workflow.

Comment thread tools/packaging/Dockerfile.distroless-dev Outdated
Comment thread src/server/CMakeLists.txt Outdated
Comment thread src/server/dfly_main.cc Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread Makefile Outdated
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from 3b3a716 to 2a94998 Compare March 17, 2026 07:23
Comment thread src/server/CMakeLists.txt Outdated
@romange
Copy link
Copy Markdown
Collaborator

romange commented Mar 17, 2026

please note I do not want to switch to distroless right away - but we can replace internal tools in our current image.

Comment thread src/server/dfly_healthcheck.cc Outdated
@@ -0,0 +1,126 @@
// Copyright 2024, DragonflyDB authors. All rights reserved.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commonly Used Minimal Health Checkers:
microcheck: A suite of tiny (~75 KB) C-compiled binaries including httpcheck, httpscheck, and portcheck. They offer configurable settings, such as response codes and timeout, and provide standard exit codes (0 for healthy, 1 for unhealthy).
dmikusa/tiny-health-checker: A very small binary written in Go designed to only make HTTP requests to localhost. It is specifically designed to be safe and difficult to misuse if a container is compromised.
cloudlena/healthcheck: An extension of the scratch Docker image that adds a built-in HTTP health check tool, adding only about 2.8MB to the image size.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to add an additional binary regardless of our new binary or a tiny external check.
All these external checks can check the HTTP request or just open a TCP port. In this case, our binary is preferable because it can check the health using the PING command. /healthz handler has already been removed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about tls configured datastore?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general I do have preference to package an external binary if it does the same as we do not want to reinvent the wheel and their static tool chain is probably better than of C++.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed
I added httpcheck and httpscheck in the image, but httpcheck is used by default.

Copilot AI review requested due to automatic review settings March 17, 2026 08:38
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from 2a94998 to 96f9a70 Compare March 17, 2026 08:38
@vyavdoshenko vyavdoshenko force-pushed the bobik/distroless_container branch from 96f9a70 to a56605e Compare March 17, 2026 08:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds distroless-based Docker images and a standalone dragonfly-healthcheck binary to enable HEALTHCHECK CMD in shell-less containers, plus a /healthz HTTP endpoint and a Linux-only privilege drop flag.

Changes:

  • Add distroless dev/prod Dockerfiles that include dragonfly + dragonfly-healthcheck and use an exec-form Docker healthcheck.
  • Add /healthz HTTP handler and a new dragonfly-healthcheck standalone binary.
  • Add Linux --user flag to drop privileges (and chown the data dir) after binding ports; extend build/CI to build the new binary and distroless flavor.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tools/packaging/Dockerfile.distroless-prod New distroless production image that copies release artifacts + libz and configures exec-form healthcheck.
tools/packaging/Dockerfile.distroless-dev New distroless dev image built from source; includes healthcheck binary and libz.
tools/docker/fetch_release.sh Minor formatting/line fix; still stages the release binary into /build.
src/server/main_service.cc Adds unauthenticated /healthz HTTP handler and exempts it from auth.
src/server/dfly_main.cc Adds --user flag and privilege-drop/chown logic on Linux before service.Init().
src/server/dfly_healthcheck.cc New minimal HTTP /healthz probe binary for distroless HEALTHCHECK.
src/server/CMakeLists.txt Adds dragonfly-healthcheck target.
Makefile Builds and packages dragonfly-healthcheck into release artifacts.
.github/workflows/docker-dev-release.yml Adds distroless to the dev image build matrix.
Comments suppressed due to low confidence (1)

src/server/dfly_healthcheck.cc:119

  • A single read() into a small buffer is not guaranteed to contain the full HTTP status line; packetization/short reads can make strchr(resp, ' ') fail even when the server is healthy. Read until you have a full line ("\r\n") or at least enough bytes to parse the status code, retrying on EINTR.
  return strncmp(resp, kPongReply, strlen(kPongReply)) == 0 ? 0 : 1;
}

Comment thread src/server/CMakeLists.txt Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread src/server/dfly_healthcheck.cc Outdated
Comment thread tools/packaging/Dockerfile.distroless-prod Outdated
Comment thread tools/packaging/Dockerfile.distroless-dev Outdated
Comment thread src/server/dfly_main.cc Outdated
@vyavdoshenko vyavdoshenko changed the title [WIP] feat: add distroless Docker image with built-in healthcheck feat: add distroless Docker image with built-in healthcheck Mar 17, 2026
Comment thread src/server/dfly_main.cc
Comment thread tools/packaging/Dockerfile.distroless-prod Outdated
Comment thread src/server/dfly_main.cc

ABSL_FLAG(uint16_t, tcp_backlog, 256, "TCP listen(2) backlog parameter.");
#ifdef __linux__
ABSL_FLAG(string, user, "",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it mean that distroless containers will have to run with root permissions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current state: The distroless image runs as root, same as the existing images before entrypoint.sh drops privileges. The --user flag allows dropping privileges at runtime after binding ports.

Distroless base ships a built-in nonroot user (uid 65534). We could add USER nonroot to the Dockerfile, but there are trade-offs:

  • Volume mounts (-v /host/path:/data) may not be writable by uid 65534 on the host - root doesn't have this problem
  • The /data directory ownership needs to be set up at build time (extra COPY --chown layer)
  • The --user flag for runtime privilege dropping becomes less useful (can't chown as non-root)

For now, running as root in distroless is less risky than in ubuntu - there's no shell, no package manager, no writable system paths. Users who require non-root can use docker run --user 65534 or Kubernetes securityContext.runAsUser.

I can add USER nonroot if you prefer - let me know.

@vyavdoshenko vyavdoshenko requested a review from romange March 27, 2026 16:36
Copy link
Copy Markdown
Collaborator

@romange romange left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, we really need to improve our http strategy by going over use-cases like this one and public datastores (with tls) and understand how we should configure http access.

Comment thread src/server/dfly_main.cc
};
do_chown(data_dir);
std::error_code ec;
fs::recursive_directory_iterator it(data_dir, ec);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I run claude /review on this, as I do not have much knowledge and it gave me good comments, I think:

  1. dfly_main.cc:230-238 — When a numeric UID is passed (e.g. --user=1000), getpwuid() is called. If it returns NULL (UID not in /etc/passwd), the function fails and the server exits. This is very common in
    containers — distroless only has root and nobody in passwd. A user running --user=65534 (nobody) works, but --user=1000 fails even though it's a perfectly valid UID for setuid. Consider falling back to using the raw UID with setgroups(0, nullptr) when getpwuid returns NULL, similar to the more complete version discussed in earlier review rounds. Without this, the --user flag is
    significantly less useful in the very containers it's designed for.

  2. /healthz requires primary_port_http_enabled — silent failure

The /healthz handler is registered via ConfigureHttpHandlers (main_service.cc:2797), which only runs when the HTTP console is active on the port. The HTTP listener is only created when
primary_port_http_enabled=true (default) or on the admin port (dragonfly_listener.cc:162-163).
worth documenting in the Dockerfile comments.

  1. HEALTHCHECK --start-period=30s is too short for large snapshots

Dockerfile.distroless-dev:47 — During snapshot loading, gstate() == GlobalState::LOADING (common_types.h:29), so /healthz returns 503. After the 30s start period, Docker begins counting health failures. With
--retries=3 --interval=15s, the container gets 75s total (30 + 3*15) before being marked unhealthy. Large snapshots can take minutes to load. Worth noting in the Dockerfile comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. fixed
  2. the comment was updated
  3. the comment was updated

Comment thread src/server/dfly_main.cc Outdated
#ifdef __linux__
#include <grp.h>
#include <pwd.h>
#include <unistd.h>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since unistd.h is a POSIX header (not Linux-specific), it could stay outside the guard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Comment thread src/server/dfly_main.cc
};
do_chown(data_dir);
std::error_code ec;
fs::recursive_directory_iterator it(data_dir, ec);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use error_code overload for iterating:

 std::error_code ec;                                                                                                                                                                                              
  fs::recursive_directory_iterator it(data_dir, ec);                                                                                                                                                               
  for (; !ec && it != fs::recursive_directory_iterator(); it.increment(ec)) {
    do_chown(it->path());                                                                                                                                                                                          
  }                                                                                                                                                                                                                
  if (ec) {                                                                                                                                                                                                        
    LOG(WARNING) << "Error iterating " << data_dir << ": " << ec.message();                                                                                                                                        
  }                                                                                

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@vyavdoshenko vyavdoshenko requested a review from romange March 28, 2026 14:49
@vyavdoshenko vyavdoshenko merged commit 474974f into main Mar 28, 2026
12 of 13 checks passed
@vyavdoshenko vyavdoshenko deleted the bobik/distroless_container branch March 28, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants