monorepo with packages with independent dependencies using uv#3
monorepo with packages with independent dependencies using uv#3thromer wants to merge 6 commits into
Conversation
WalkthroughThis change restructures the Python monorepo from a previous "lib-new/app-new" model to a new workspace-based layout with separate Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant Repo as Monorepo
participant UV as uv Tool
participant Docker as Docker Engine
participant GCR as Google Artifact Registry
participant CloudRun as Google Cloud Run
Dev->>Repo: Clone and bootstrap workspace
Dev->>UV: Run uv init . and uv sync
UV->>Repo: Set up virtual environments and dependencies
Dev->>Docker: Build Docker image for app (e.g., a1)
Docker->>GCR: Push image to Artifact Registry
Dev->>CloudRun: Deploy image using deploy.sh
CloudRun->>Dev: Service deployed and available
Possibly related PRs
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 21
🔭 Outside diff range comments (1)
libs/l2/tests/test_l2.py (1)
5-6: Remove trailing blank line.-
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (3)
app-new/uv.lockis excluded by!**/*.locklib-new/uv.lockis excluded by!**/*.lockuv.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
.dockerignore(1 hunks)Dockerfile(0 hunks)README.md(1 hunks)app-new/app_new/__init__.py(0 hunks)app-new/pyproject.toml(0 hunks)apps/a1/Dockerfile(1 hunks)apps/a1/Dockerfile.merged(1 hunks)apps/a1/a1/__init__.py(1 hunks)apps/a1/pyproject.toml(1 hunks)apps/a2/a2/__init__.py(1 hunks)apps/a2/pyproject.toml(1 hunks)lib-new/pyproject.toml(0 hunks)lib-new/src/lib_new/__init__.py(0 hunks)libs/l1/.python-version(1 hunks)libs/l1/pyproject.toml(1 hunks)libs/l1/src/l1/__init__.py(1 hunks)libs/l1/tests/test_l1.py(1 hunks)libs/l2/.python-version(1 hunks)libs/l2/pyproject.toml(1 hunks)libs/l2/src/l2/__init__.py(1 hunks)libs/l2/tests/test_l2.py(1 hunks)pyproject.toml(1 hunks)
💤 Files with no reviewable changes (5)
- app-new/app_new/init.py
- lib-new/src/lib_new/init.py
- lib-new/pyproject.toml
- app-new/pyproject.toml
- Dockerfile
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:20-20
Timestamp: 2025-06-29T18:28:46.875Z
Learning: uv workspaces provide elegant monorepo management in Docker by using a root pyproject.toml with members configuration and uv sync command, eliminating the need for manual export/build/install steps for each package.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:36:26.385Z
Learning: In Docker builds using uv sync, editable packages are installed as .pth files that reference source locations, not actual distributions. This breaks in containerized environments where the source paths don't exist. Use uv build + pip install for proper package installation in containers.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:45:02.721Z
Learning: uv export --no-editable still includes local package references (like "." and "../package-name") in the requirements.txt output, which breaks pip install in containers. These need to be filtered out with sed or similar tools.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
libs/l2/.python-version (1)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
libs/l1/.python-version (1)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
README.md (3)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:20-20
Timestamp: 2025-06-29T18:28:46.875Z
Learning: uv workspaces provide elegant monorepo management in Docker by using a root pyproject.toml with members configuration and uv sync command, eliminating the need for manual export/build/install steps for each package.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:36:26.385Z
Learning: In Docker builds using uv sync, editable packages are installed as .pth files that reference source locations, not actual distributions. This breaks in containerized environments where the source paths don't exist. Use uv build + pip install for proper package installation in containers.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
libs/l1/pyproject.toml (1)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
apps/a2/pyproject.toml (1)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
apps/a1/pyproject.toml (1)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
pyproject.toml (4)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:20-20
Timestamp: 2025-06-29T18:28:46.875Z
Learning: uv workspaces provide elegant monorepo management in Docker by using a root pyproject.toml with members configuration and uv sync command, eliminating the need for manual export/build/install steps for each package.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:36:26.385Z
Learning: In Docker builds using uv sync, editable packages are installed as .pth files that reference source locations, not actual distributions. This breaks in containerized environments where the source paths don't exist. Use uv build + pip install for proper package installation in containers.
apps/a1/Dockerfile (4)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:20-20
Timestamp: 2025-06-29T18:28:46.875Z
Learning: uv workspaces provide elegant monorepo management in Docker by using a root pyproject.toml with members configuration and uv sync command, eliminating the need for manual export/build/install steps for each package.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:36:26.385Z
Learning: In Docker builds using uv sync, editable packages are installed as .pth files that reference source locations, not actual distributions. This breaks in containerized environments where the source paths don't exist. Use uv build + pip install for proper package installation in containers.
apps/a1/Dockerfile.merged (2)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
🧬 Code Graph Analysis (6)
libs/l2/src/l2/__init__.py (1)
libs/l1/src/l1/__init__.py (1)
hello(2-3)
apps/a2/a2/__init__.py (2)
libs/l2/src/l2/__init__.py (1)
hello(2-3)apps/a1/a1/__init__.py (1)
main(2-3)
libs/l2/tests/test_l2.py (1)
libs/l2/src/l2/__init__.py (1)
hello(2-3)
libs/l1/tests/test_l1.py (1)
libs/l1/src/l1/__init__.py (1)
hello(2-3)
apps/a1/a1/__init__.py (1)
libs/l1/src/l1/__init__.py (1)
hello(2-3)
libs/l1/src/l1/__init__.py (1)
libs/l2/src/l2/__init__.py (1)
hello(2-3)
🪛 Flake8 (7.2.0)
libs/l2/src/l2/__init__.py
[error] 1-1: 'requests' imported but unused
(F401)
[error] 2-2: expected 2 blank lines, found 0
(E302)
apps/a2/a2/__init__.py
[error] 2-2: expected 2 blank lines, found 0
(E302)
libs/l2/tests/test_l2.py
[error] 3-3: expected 2 blank lines, found 1
(E302)
[warning] 5-5: blank line at end of file
(W391)
libs/l1/tests/test_l1.py
[error] 3-3: expected 2 blank lines, found 1
(E302)
apps/a1/a1/__init__.py
[error] 2-2: expected 2 blank lines, found 0
(E302)
libs/l1/src/l1/__init__.py
[error] 2-2: expected 2 blank lines, found 0
(E302)
🪛 Ruff (0.11.9)
libs/l2/src/l2/__init__.py
1-1: requests imported but unused
(F401)
🪛 markdownlint-cli2 (0.17.2)
README.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
4-4: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
4-4: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
9-9: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
9-9: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 Checkov (3.2.334)
apps/a1/Dockerfile
[LOW] 6-6: Ensure the base image uses a non latest version tag
(CKV_DOCKER_7)
[LOW] 9-9: Ensure the last USER is not root
(CKV_DOCKER_8)
[LOW] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
apps/a1/Dockerfile.merged
[LOW] 2-2: Ensure the base image uses a non latest version tag
(CKV_DOCKER_7)
[LOW] 1-9: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
[LOW] 1-9: Ensure that a user for the container has been created
(CKV_DOCKER_3)
🪛 Hadolint (2.12.0)
apps/a1/Dockerfile
[warning] 6-6: Always tag the version of an image explicitly
(DL3006)
[warning] 9-9: Last USER should not be root
(DL3002)
[error] 19-19: COPY with more than 2 arguments requires the last argument to end with /
(DL3021)
apps/a1/Dockerfile.merged
[warning] 2-2: Always tag the version of an image explicitly
(DL3006)
[warning] 4-4: COPY --from should reference a previously defined FROM alias
(DL3022)
🔇 Additional comments (2)
.dockerignore (1)
2-2: LGTM – harmless but useful exclusion
Ignoring nested.dockerignorefiles reduces context size and has no side-effects.pyproject.toml (1)
1-17: Workspace Python floor (≥3.13) clashes with dev interpreters (3.11)
Becauseuvchecksrequires-python, any contributor still on 3.11 (as encouraged by the new.python-versionfiles) will be blocked. Decide on a single lower bound:
- Drop the floor to
>=3.11; or- Upgrade every
.python-versionto 3.13.Whichever you choose, keep it consistent across root & members to avoid CI surprises.
| @@ -0,0 +1 @@ | |||
| 3.11 | |||
There was a problem hiding this comment.
Version declared here conflicts with package & workspace requirements
libs/l1/pyproject.toml (≥3.13) and the root workspace (≥3.13) cannot be satisfied when the local pyenv version is pinned to 3.11. uv sync/pyright will refuse to run under 3.11.
-3.11
+3.13Either lift the requires-python floor back to 3.11 in all affected pyproject.toml files or bump the local version to 3.13 to keep the tool-chain happy.
📝 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.
| 3.11 | |
| 3.13 |
🤖 Prompt for AI Agents
In libs/l1/.python-version at line 1, the Python version is set to 3.11, which
conflicts with the minimum Python version requirement of 3.13 specified in
libs/l1/pyproject.toml and the root workspace. To fix this, update the version
in .python-version to 3.13 to align with the package and workspace requirements,
ensuring compatibility with uv sync and pyright tools.
| @@ -0,0 +1 @@ | |||
| 3.11 | |||
There was a problem hiding this comment.
Same version drift as in l1
Pinning 3.11 here while the library advertises requires-python = ">=3.13" will break installs/tests in the workspace. Align the numbers as suggested for l1.
🤖 Prompt for AI Agents
In libs/l2/.python-version at line 1, the Python version is pinned to 3.11,
which conflicts with the library's advertised requirement of Python >=3.13.
Update the version in this file to 3.13 to align with the declared Python
version requirement and ensure consistency across the workspace.
| Bootstrap | ||
|
|
||
| Once per machine | ||
| ``` | ||
| curl -LsSf https://astral.sh/uv/install.sh | sh | ||
| ``` | ||
|
|
||
| Once per repo | ||
| ``` | ||
| uv init . | ||
| uv sync | ||
| ``` | ||
|
|
||
| Maybe a (slightly?) different approach will work, WIP [Claude conversation](https://claude.ai/chat/414e992f-a883-4721-8283-8bdb7e961a01) | ||
|
|
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Minor Markdown-lint issues – add heading & fence languages
-Bootstrap
+# Bootstrapand add language hints / blank lines:
-```
+```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
Same for the later code block. Improves readability and satisfies MD031 / MD040.
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
---
4-4: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
---
4-4: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
---
9-9: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
---
9-9: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In README.md lines 1 to 15, the code blocks lack language specifiers and proper
spacing, which causes markdown lint issues. Add "bash" after the opening triple
backticks for both code blocks and ensure there is a blank line before and after
each fenced code block to improve readability and comply with markdown lint
rules MD031 and MD040.
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
<!-- fingerprinting:phantom:triton:cougar -->
| from l2 import hello | ||
| def main() -> None: | ||
| print(hello("no moo a2")) |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Fix PEP 8 formatting: add blank lines before function definition.
The static analysis correctly identifies a missing blank line before the function definition.
from l2 import hello
+
+
def main() -> None:
print(hello("no moo a2"))📝 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.
| from l2 import hello | |
| def main() -> None: | |
| print(hello("no moo a2")) | |
| from l2 import hello | |
| def main() -> None: | |
| print(hello("no moo a2")) |
🧰 Tools
🪛 Flake8 (7.2.0)
[error] 2-2: expected 2 blank lines, found 0
(E302)
🤖 Prompt for AI Agents
In apps/a2/a2/__init__.py at lines 1 to 3, the function definition for main()
lacks a blank line before it, violating PEP 8 style guidelines. Add a blank line
after the import statement and before the def main() line to separate the import
from the function definition properly.
| def hello(message: str) -> str: | ||
| return f'Hello, {message}' |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Fix PEP 8 formatting: add blank lines before function definition.
+
+
def hello(message: str) -> str:
return f'Hello, {message}'📝 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.
| def hello(message: str) -> str: | |
| return f'Hello, {message}' | |
| def hello(message: str) -> str: | |
| return f'Hello, {message}' |
🧰 Tools
🪛 Flake8 (7.2.0)
[error] 2-2: expected 2 blank lines, found 0
(E302)
🤖 Prompt for AI Agents
In libs/l2/src/l2/__init__.py around lines 2 to 3, the function definition lacks
the required blank lines before it according to PEP 8 style guidelines. Add two
blank lines before the 'def hello' function to comply with PEP 8 formatting
standards.
| # Build | ||
| # We use the same image as the runtime base image so that Python | ||
| # version and location matches. Might save on data transfer costs. | ||
| FROM us-west1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/python313 AS builder |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Pin the base image with a digest for reproducibility.
python313 is explicit but mutable; tomorrow’s push could break your build.
Add an immutable digest (or at least a dated tag) to guarantee identical layers.
🧰 Tools
🪛 Checkov (3.2.334)
[LOW] 6-6: Ensure the base image uses a non latest version tag
(CKV_DOCKER_7)
🪛 Hadolint (2.12.0)
[warning] 6-6: Always tag the version of an image explicitly
(DL3006)
🤖 Prompt for AI Agents
In apps/a1/Dockerfile at line 6, the base image is currently referenced by a
mutable tag 'python313', which can lead to inconsistent builds. To fix this,
replace the tag with a specific immutable digest or a dated tag for the base
image to ensure reproducibility and prevent unexpected changes in future builds.
| FROM scratch | ||
| COPY --from=builder /workspace/.venv /workspace/.venv | ||
|
|
||
| ENV PYTHONPATH=/workspace/.venv/lib/python3.13/site-packages | ||
| ENV PATH=/workspace/.venv/bin:$PATH | ||
|
|
||
| CMD ["a1"] |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Missing HEALTHCHECK and non-root runtime user.
A scratch image inherits root; add:
USER 65532 # non-root
HEALTHCHECK CMD a1 --help || exit 1to satisfy CKV_DOCKER_2 & CKV_DOCKER_8 and to surface liveness issues early.
🧰 Tools
🪛 Checkov (3.2.334)
[LOW] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🤖 Prompt for AI Agents
In apps/a1/Dockerfile around lines 27 to 33, the Dockerfile uses the scratch
base image which runs as root by default and lacks a HEALTHCHECK instruction. To
fix this, add a non-root user directive by including 'USER 65532' and add a
HEALTHCHECK instruction like 'HEALTHCHECK CMD a1 --help || exit 1' to enable
early detection of liveness issues and comply with CKV_DOCKER_2 and
CKV_DOCKER_8.
| # Sorta hacky solution to being able to write to /workspace | ||
| USER root | ||
|
|
||
| COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv |
There was a problem hiding this comment.
🛠️ Refactor suggestion
uv:latest tag is volatile.
Same reproducibility concern as above—pin ghcr.io/astral-sh/uv:<version>@<digest> instead of latest.
🤖 Prompt for AI Agents
In apps/a1/Dockerfile at line 11, the COPY command uses the volatile 'latest'
tag for the uv image, which can cause reproducibility issues. Replace 'latest'
with a specific version tag and digest for the uv image, such as
'ghcr.io/astral-sh/uv:<version>@<digest>', to ensure consistent builds.
| UV_LINK_MODE=copy \ | ||
| UV_NO_MANAGED_PYTHON=1 | ||
|
|
||
| COPY pyproject.toml uv.lock . |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Fix multi-source COPY to satisfy Hadolint DL3021.
The linter expects the destination to end in /.
Quick fix:
-COPY pyproject.toml uv.lock .
+COPY pyproject.toml uv.lock ./📝 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.
| COPY pyproject.toml uv.lock . | |
| COPY pyproject.toml uv.lock ./ |
🧰 Tools
🪛 Hadolint (2.12.0)
[error] 19-19: COPY with more than 2 arguments requires the last argument to end with /
(DL3021)
🤖 Prompt for AI Agents
In apps/a1/Dockerfile at line 19, the multi-source COPY command lacks a trailing
slash on the destination, which violates Hadolint rule DL3021. Fix this by
appending a slash to the destination path, changing it to COPY pyproject.toml
uv.lock ./ to clearly indicate the destination is a directory.
| USER root | ||
|
|
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Builder still runs as root.
Static analysis rightfully warns (DL3002).
Create a non-privileged user after the COPY, switch back with USER builder, and keep root only where absolutely required.
🏁 Script executed:
#!/bin/bash
# Locate the Dockerfile for app a1 and display its contents with line numbers
FILE=$(find . -type f -path "*/apps/a1/Dockerfile")
if [[ -z "$FILE" ]]; then
echo "apps/a1/Dockerfile not found"
exit 1
fi
echo "Found Dockerfile at: $FILE"
nl -ba "$FILE"Length of output: 322
🏁 Script executed:
#!/bin/bash
# Display the Dockerfile contents with line numbers using sed and cat -n
FILE=$(find . -type f -path "*/apps/a1/Dockerfile")
if [[ -z "$FILE" ]]; then
echo "apps/a1/Dockerfile not found"
exit 1
fi
echo "Found Dockerfile at: $FILE"
sed -n '1,200p' "$FILE" | cat -nLength of output: 1424
Run the builder stage as a non-root user
The builder stage is still running as root (USER root on line 9), which triggers DL3002. We should minimize privileged operations by:
• Creating a dedicated, unprivileged user/group in the builder stage
• Chown’ing /workspace (and any other dirs) to that user
• Switching to that user for all build steps
• Only elevating back to root for operations that truly require it, then reverting to the unprivileged user
Modify apps/a1/Dockerfile around lines 6–13:
FROM us-west1-docker.pkg.dev/serverless-runtimes/google-22/runtimes/python313 AS builder
-# Sorta hacky solution to being able to write to /workspace
-USER root
+### create an unprivileged builder user and own workspace
+RUN addgroup --system builder \
+ && adduser --system --ingroup builder builder \
+ && mkdir -p /workspace \
+ && chown -R builder:builder /workspace
+
+USER builder
# install UV (requires root)
-USER root
+USER root
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
+USER builder
WORKDIR /workspace🧰 Tools
🪛 Checkov (3.2.334)
[LOW] 9-9: Ensure the last USER is not root
(CKV_DOCKER_8)
🪛 Hadolint (2.12.0)
[warning] 9-9: Last USER should not be root
(DL3002)
🤖 Prompt for AI Agents
In apps/a1/Dockerfile around lines 6 to 13, the builder stage is running as
root, which is a security risk. Fix this by creating a dedicated non-root user
and group in the builder stage, changing ownership of /workspace and any other
necessary directories to this user, switching to this user for all build steps,
and only using root temporarily for operations that require elevated privileges
before switching back to the unprivileged user.
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
deploy.sh(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:20-20
Timestamp: 2025-06-29T18:28:46.875Z
Learning: uv workspaces provide elegant monorepo management in Docker by using a root pyproject.toml with members configuration and uv sync command, eliminating the need for manual export/build/install steps for each package.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:36:26.385Z
Learning: In Docker builds using uv sync, editable packages are installed as .pth files that reference source locations, not actual distributions. This breaks in containerized environments where the source paths don't exist. Use uv build + pip install for proper package installation in containers.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:0-0
Timestamp: 2025-06-29T19:45:02.721Z
Learning: uv export --no-editable still includes local package references (like "." and "../package-name") in the requirements.txt output, which breaks pip install in containers. These need to be filtered out with sed or similar tools.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: lib-new/.python-version:1-1
Timestamp: 2025-06-29T18:50:41.165Z
Learning: In the uv-monorepo-example project, Python version differences between local development (.python-version files specifying 3.11) and Docker runtime (3.12) are intentional. The user uses 3.11 for local development consistency and plans to use CI/CD to ensure 3.12 compatibility.
deploy.sh (2)
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:28-30
Timestamp: 2025-06-29T22:22:29.398Z
Learning: In the uv-monorepo-example project, the main Dockerfile intentionally creates a minimal scratch-based image containing only Python packages (without the Python interpreter). This is deployed using a separate runtime image (like Dockerfile.merged) that provides the OS and Python interpreter, then copies the packages from the built artifact. This two-stage pattern separates package building from runtime concerns and allows flexible deployment targets.
Learnt from: thromer
PR: thromer/uv-monorepo-example#1
File: Dockerfile:20-20
Timestamp: 2025-06-29T18:28:46.875Z
Learning: uv workspaces provide elegant monorepo management in Docker by using a root pyproject.toml with members configuration and uv sync command, eliminating the need for manual export/build/install steps for each package.
🔇 Additional comments (1)
deploy.sh (1)
31-31: Verify--base-imageflag support ingcloud run deploy
gcloud run deploy(stable & beta channels) currently lacks a--base-imageflag. If you’re relying on an experimental CLI, pin the version or switch to--execution-environment/ Dockerfile-based runtime selection.
| @@ -0,0 +1,41 @@ | |||
| #!/usr/bin/bash | |||
There was a problem hiding this comment.
🧹 Nitpick (assertive)
Use portable she-bang
#!/usr/bin/bash only exists on some distros. Prefer /usr/bin/env bash for portability across build agents & CI runners.
-#!/usr/bin/bash
+#!/usr/bin/env bash📝 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.
| #!/usr/bin/bash | |
| #!/usr/bin/env bash |
🤖 Prompt for AI Agents
In deploy.sh at line 1, replace the she-bang line from #!/usr/bin/bash to
#!/usr/bin/env bash to ensure portability across different Linux distributions
and CI environments.
| @@ -0,0 +1,41 @@ | |||
| #!/usr/bin/bash | |||
|
|
|||
| set -o pipefail | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add -euo for safer Bash execution
set -o pipefail alone does not abort on command failures or unset vars. Fail-fast behaviour is critical for deployment scripts.
-set -o pipefail
+set -euo pipefail📝 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.
| set -o pipefail | |
| set -euo pipefail |
🤖 Prompt for AI Agents
In deploy.sh at line 3, the script currently uses 'set -o pipefail' but lacks
'set -euo' which ensures the script fails fast on errors, unset variables, or
pipeline failures. Update the line to include 'set -euo pipefail' to enable
safer Bash execution by aborting on command failures and unset variables.
| REPO=projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts | ||
|
|
||
| ensure_repo() { | ||
| repo=projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts | ||
| if gcloud artifacts repositories describe $repo >& /dev/null; then | ||
| return 0 | ||
| fi | ||
| gcloud artifacts repositories create \ | ||
| --location=$LOCATION \ | ||
| --repository-format=DOCKER \ | ||
| $REPO && | ||
| gcloud artifacts repositories set-cleanup-policies \ | ||
| --policy=../repository-cleanup-policy.json \ | ||
| $REPO | ||
| return $? | ||
| } |
There was a problem hiding this comment.
ensure_repo never called & $REPO unused
The repository may not exist, causing the subsequent docker push to fail. Call the helper early and drop the duplicate local variable.
-REPO=projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts
+REPO="projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts"
...
-ensure_repo() {
- repo=projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts
+ensure_repo() {
+ repo="$REPO"
if gcloud artifacts repositories describe "$repo" >& /dev/null; then
return 0
fi
gcloud artifacts repositories create \
--location="$LOCATION" \
--repository-format=DOCKER \
"$REPO" &&
gcloud artifacts repositories set-cleanup-policies \
--policy=../repository-cleanup-policy.json \
"$REPO"
return $?
}
+# ensure repository exists before build/push
+ensure_repo📝 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.
| REPO=projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts | |
| ensure_repo() { | |
| repo=projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts | |
| if gcloud artifacts repositories describe $repo >& /dev/null; then | |
| return 0 | |
| fi | |
| gcloud artifacts repositories create \ | |
| --location=$LOCATION \ | |
| --repository-format=DOCKER \ | |
| $REPO && | |
| gcloud artifacts repositories set-cleanup-policies \ | |
| --policy=../repository-cleanup-policy.json \ | |
| $REPO | |
| return $? | |
| } | |
| REPO="projects/${PROJECT}/locations/${LOCATION}/repositories/artifacts" | |
| ensure_repo() { | |
| repo="$REPO" | |
| if gcloud artifacts repositories describe "$repo" >& /dev/null; then | |
| return 0 | |
| fi | |
| gcloud artifacts repositories create \ | |
| --location="$LOCATION" \ | |
| --repository-format=DOCKER \ | |
| "$REPO" && | |
| gcloud artifacts repositories set-cleanup-policies \ | |
| --policy=../repository-cleanup-policy.json \ | |
| "$REPO" | |
| return $? | |
| } | |
| # ensure repository exists before build/push | |
| ensure_repo |
🤖 Prompt for AI Agents
In deploy.sh around lines 8 to 23, the ensure_repo function is defined but never
called, and the local variable repo inside it duplicates the global REPO
variable. To fix this, remove the local repo variable inside ensure_repo and
replace its usage with the global REPO variable. Then, call ensure_repo early in
the script before any docker push commands to ensure the repository exists and
prevent push failures.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores