Skip to content

fix(api): multi-stage build, non-root user, add healthcheck#612

Open
pavsoss wants to merge 1 commit into
sreerevanth:mainfrom
pavsoss:issue-594-dockerfile-security
Open

fix(api): multi-stage build, non-root user, add healthcheck#612
pavsoss wants to merge 1 commit into
sreerevanth:mainfrom
pavsoss:issue-594-dockerfile-security

Conversation

@pavsoss

@pavsoss pavsoss commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Closes #594

This PR addresses the security vulnerabilities in Dockerfile.api by:

  • Using a multi-stage build: Compiles wheels in a builder stage and only copies them into the runtime image, keeping tools like build-essential and git out of the final container.
  • Dropping privileges: Runs the container as a dedicated agentwatch non-root user (uid 10001) instead of root.
  • Adding a Healthcheck: Implemented a native Python-based HEALTHCHECK (using urllib.request) to verify the API is up without needing to install curl.

Summary by CodeRabbit

  • Bug Fixes

    • Improved container health monitoring through an explicit application health check.
  • Security

    • The application container now runs as a non-root user.
  • Performance

    • Reduced runtime image contents by installing only prebuilt application packages and removing unnecessary runtime dependencies.

@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

@pavsoss is attempting to deploy a commit to the sreerevanth's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Dockerfile.api now uses a multi-stage build, installs prebuilt wheels in the runtime image, removes build-only packages, runs as agentwatch, exposes port 8000, and checks /health.

Changes

API container hardening

Layer / File(s) Summary
Multi-stage runtime image
Dockerfile.api
The builder stage creates wheel artifacts, while the runtime stage installs them without build tooling, switches to the non-root agentwatch user, exposes port 8000, and adds a /health health check.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • sreerevanth/AgentWatch#113: Both changes update Dockerfile.api runtime configuration for launching the application on port 8000.

Poem

A rabbit hops through wheels so neat,
No root-run shell beneath its feet.
Port eight thousand hums alive,
/health checks that services thrive.
Build tools stay outside the door—
Safe carrots for the runtime floor!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main Dockerfile.api changes: multi-stage build, non-root user, and healthcheck.
Linked Issues check ✅ Passed The Dockerfile now builds wheels in a separate stage, runs as agentwatch, removes build tools from runtime, and adds a HEALTHCHECK, matching #594.
Out of Scope Changes check ✅ Passed Only Dockerfile.api changed, and the edits align with the requested container hardening and health-monitoring work.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 PR Test Results

Check Result
Tests (pytest tests/) ✅ success
Lint (ruff check .) ❌ failure
Coverage (agentwatch) 74.19%

Python 3.12 · commit 5a05591

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Dockerfile.api (1)

14-15: 🩺 Stability & Availability | 🔵 Trivial

Consider a longer --start-period for Python application startup.

The --start-period=5s grace window may be too short for a Python/FastAPI application that initializes database sessions, Redis connections, and other subsystems before the /health endpoint becomes responsive. If the app takes longer than 5 seconds to bind port 8000, the healthcheck will begin failing immediately, and orchestrators (e.g., Kubernetes, Docker Swarm) may mark the container unhealthy and restart it before it finishes starting. A value of 15s30s is more typical for Python web applications.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.api` around lines 14 - 15, Increase the HEALTHCHECK --start-period
from 5s to a 15s–30s grace window, preferably 30s, so the Python application can
complete initialization before healthcheck failures count.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Dockerfile.api`:
- Around line 14-15: Update the Dockerfile healthcheck’s urllib.request.urlopen
call to pass timeout=5, and wrap the request in exception handling that
suppresses HTTPError and other expected healthcheck failures while still exiting
non-zero. Keep successful /health responses passing and ensure failures complete
within Docker’s 10-second timeout without traceback output.

---

Nitpick comments:
In `@Dockerfile.api`:
- Around line 14-15: Increase the HEALTHCHECK --start-period from 5s to a
15s–30s grace window, preferably 30s, so the Python application can complete
initialization before healthcheck failures count.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 99332262-2cd4-4a9b-bd9b-9545bbada46d

📥 Commits

Reviewing files that changed from the base of the PR and between a492da5 and 5a05591.

📒 Files selected for processing (1)
  • Dockerfile.api

Comment thread Dockerfile.api
Comment on lines +14 to +15
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a timeout to the urlopen call in the healthcheck.

urllib.request.urlopen is invoked without a timeout argument. If the API accepts the TCP connection but hangs before responding, the Python process will block until Docker's --timeout=10s kills it — producing a less informative timeout failure and holding a connection open longer than necessary. Passing timeout=5 ensures the check fails fast and cleanly within the Docker timeout window.

Additionally, when the /health endpoint returns 503 (degraded), urlopen raises HTTPError and Python prints a full traceback to stderr on every check interval, which clutters container logs. Wrapping in a try/except would suppress the noise while still exiting non-zero.

🛡️ Proposed healthcheck improvement
 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
-    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
+    CMD python -c "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health', timeout=5).status == 200 else 1)" || exit 1
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health', timeout=5).status == 200 else 1)" || exit 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.api` around lines 14 - 15, Update the Dockerfile healthcheck’s
urllib.request.urlopen call to pass timeout=5, and wrap the request in exception
handling that suppresses HTTPError and other expected healthcheck failures while
still exiting non-zero. Keep successful /health responses passing and ensure
failures complete within Docker’s 10-second timeout without traceback output.

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.

[SECURITY] Dockerfile.api runs as root and ships build tooling into the production image

1 participant