Python-Ruby Http Distroless - #454
cosmintanasa47 wants to merge 28 commits into
Conversation
Kept the non-distroless variant's Kraftfile.
Kept the same source file.
Slightly modify to fit the distroless version.
There was a problem hiding this comment.
🟡 Changes recommended
At least one new example is not runnable as-written (FastAPI distroless WORKDIR breaks src.server:app import) and the Flask+SQLite distroless app is missing a configured secret key despite using flash().
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR adds new “distroless” HTTP server examples (Ruby and multiple Python frameworks) for deployment on Unikraft Cloud, along with end-to-end tests that mirror the documented build/run steps.
Changes:
- Introduces distroless example directories for Ruby 3.2 and Python 3.12 (stdlib, Flask, Flask+SQLite, FastAPI, Django).
- Adds Dockerfiles/Kraftfiles/READMEs and minimal server implementations for each new example.
- Adds end-to-end pytest coverage to validate each example serves expected content via Unikraft Cloud.
File summaries
| File | Description |
|---|---|
| httpserver-ruby3.2-distroless/test_httpserver-ruby3.2-distroless.py | E2E test for Ruby distroless example. |
| httpserver-ruby3.2-distroless/server.rb | Minimal Ruby TCP HTTP server implementation. |
| httpserver-ruby3.2-distroless/README.md | Deployment and usage guide for Ruby distroless example. |
| httpserver-ruby3.2-distroless/Kraftfile | Unikraft Cloud spec for Ruby distroless example. |
| httpserver-ruby3.2-distroless/Dockerfile | Builds Ruby distroless rootfs including server. |
| httpserver-python3.12-flask3.0-sqlite-distroless/test_httpserver-python3.12-flask3.0-sqlite-distroless.py | E2E test for Flask+SQLite distroless example. |
| httpserver-python3.12-flask3.0-sqlite-distroless/templates/post.html | FlaskBlog post template. |
| httpserver-python3.12-flask3.0-sqlite-distroless/templates/index.html | FlaskBlog index template. |
| httpserver-python3.12-flask3.0-sqlite-distroless/templates/edit.html | FlaskBlog edit form template. |
| httpserver-python3.12-flask3.0-sqlite-distroless/templates/create.html | FlaskBlog create form template. |
| httpserver-python3.12-flask3.0-sqlite-distroless/templates/base.html | Base layout template for FlaskBlog. |
| httpserver-python3.12-flask3.0-sqlite-distroless/static/css/style.css | Basic styling for FlaskBlog. |
| httpserver-python3.12-flask3.0-sqlite-distroless/server.py | FlaskBlog app implementation with SQLite backend. |
| httpserver-python3.12-flask3.0-sqlite-distroless/schema.sql | SQLite schema for posts table. |
| httpserver-python3.12-flask3.0-sqlite-distroless/requirements.txt | Python dependencies for Flask+SQLite example. |
| httpserver-python3.12-flask3.0-sqlite-distroless/README.md | Deployment and implementation guide for Flask+SQLite distroless example. |
| httpserver-python3.12-flask3.0-sqlite-distroless/Kraftfile | Unikraft Cloud spec for Flask+SQLite distroless example. |
| httpserver-python3.12-flask3.0-sqlite-distroless/init_db.py | Initializes the SQLite DB during image build. |
| httpserver-python3.12-flask3.0-sqlite-distroless/Dockerfile | Distroless build copying Python runtime + app + DB. |
| httpserver-python3.12-flask3.0-sqlite-distroless/.dockerignore | Reduces Docker build context for the example. |
| httpserver-python3.12-flask3.0-distroless/test_httpserver-python3.12-flask3.0-distroless.py | E2E test for Flask distroless example. |
| httpserver-python3.12-flask3.0-distroless/server.py | Minimal Flask “Hello, World!” server. |
| httpserver-python3.12-flask3.0-distroless/requirements.txt | Flask dependency declaration. |
| httpserver-python3.12-flask3.0-distroless/README.md | Deployment and usage guide for Flask distroless example. |
| httpserver-python3.12-flask3.0-distroless/Kraftfile | Unikraft Cloud spec for Flask distroless example. |
| httpserver-python3.12-flask3.0-distroless/Dockerfile | Distroless build for Flask example. |
| httpserver-python3.12-fastapi-0.121.3-distroless/test_httpserver-python3.12-fastapi-0.121.3-distroless.py | E2E test for FastAPI distroless example. |
| httpserver-python3.12-fastapi-0.121.3-distroless/server.py | Minimal FastAPI app. |
| httpserver-python3.12-fastapi-0.121.3-distroless/requirements.txt | Dependencies for FastAPI app. |
| httpserver-python3.12-fastapi-0.121.3-distroless/README.md | Deployment and usage guide for FastAPI distroless example. |
| httpserver-python3.12-fastapi-0.121.3-distroless/Kraftfile | Unikraft Cloud spec for FastAPI distroless example. |
| httpserver-python3.12-fastapi-0.121.3-distroless/Dockerfile | Distroless build copying Python runtime + app. |
| httpserver-python3.12-django5.0-distroless/test_httpserver-python3.12-django5.0-distroless.py | E2E test for Django distroless example (admin endpoint). |
| httpserver-python3.12-django5.0-distroless/requirements.txt | Django/bjoern/tzdata/etc dependencies. |
| httpserver-python3.12-django5.0-distroless/README.md | Deployment and implementation guide for Django distroless example. |
| httpserver-python3.12-django5.0-distroless/manage.py | Django manage.py entrypoint. |
| httpserver-python3.12-django5.0-distroless/main.py | bjoern-based WSGI server runner. |
| httpserver-python3.12-django5.0-distroless/Kraftfile | Unikraft Cloud spec for Django distroless example. |
| httpserver-python3.12-django5.0-distroless/helloworld/wsgi.py | Django WSGI configuration. |
| httpserver-python3.12-django5.0-distroless/helloworld/urls.py | Django URL configuration (admin). |
| httpserver-python3.12-django5.0-distroless/helloworld/settings.py | Django settings for the example project. |
| httpserver-python3.12-django5.0-distroless/helloworld/asgi.py | Django ASGI configuration. |
| httpserver-python3.12-django5.0-distroless/helloworld/init.py | Django project package marker. |
| httpserver-python3.12-django5.0-distroless/Dockerfile | Multi-stage build producing distroless Django image. |
| httpserver-python3.12-django5.0-distroless/.gitignore | Ignores Python cache artifacts. |
| httpserver-python3.12-django5.0-distroless/.dockerignore | Reduces Docker build context for the example. |
| httpserver-python3.12-distroless/test_httpserver-python3.12-distroless.py | E2E test for stdlib Python distroless example. |
| httpserver-python3.12-distroless/server.py | Minimal stdlib http.server-based implementation. |
| httpserver-python3.12-distroless/README.md | Deployment and usage guide for stdlib Python distroless example. |
| httpserver-python3.12-distroless/Kraftfile | Unikraft Cloud spec for stdlib Python distroless example. |
| httpserver-python3.12-distroless/Dockerfile | Distroless python base image + server copy. |
Review details
Suppressed comments (1)
httpserver-python3.12-distroless/README.md:210
- This
--imageexample uses the Flask image name (httpserver-python312-flask30) instead of this example's image name (httpserver-python312-distroless), sounikraft runwould reference the wrong image.
--image <my-org>/httpserver-python312-flask30:latest
- Files reviewed: 50/51 changed files
- Comments generated: 6
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Slightly modify to fit new distroless version.
Add multi-stage Dockerfile - first stage builds and the second executes. Chosen openeuler/distroless-ruby:3.2.2-oe2403lts for runtime, pinning to a specific SHA to avoid breaking changes from automatic updates.
Kept Kraftfile from non-distroless version.
Slightly modified non-distroless version's test file to fit the new version.
Kept source file from non-distroless version.
Modified it to fit hte distroless version.
It uses the recommended image for distroless python and it is set as nonroot for better security (less privilege - better). It is single-stage because a builder stage would be redundant, the executable is already built locally. To better understand, this would be a multi-stage Dockerfile: FROM python:3.13-slim-trixie AS builder WORKDIR /src COPY server.py /src/server.py FROM gcr.io/distroless/python3-debian13:nonroot WORKDIR /src COPY --from=builder /src/server.py /src/server.py
…e Dockerfile
Replace the manual FROM scratch base with gcr.io/distroless/cc-debian12,
which provides a Google-maintained minimal glibc runtime (libc, ld-linux,
libssl/libcrypto, ca-certificates, /etc/passwd) instead of hand-copying
every system library.
The build stage still uses python:3.12-bookworm to compile bjoern's C
extension and install dependencies; only the required artifacts are
copied into the distroless final stage:
- Python 3.12 interpreter + stdlib/site-packages (/usr/local/lib)
- libpython3.12.so.1.0, explicitly placed under
/usr/lib/x86_64-linux-gnu/ since the dynamic linker does not search
/usr/local/lib by default and Unikraft Cloud boots the binary
directly (Dockerfile ENV is not applied at runtime)
- libev.so.4 (bjoern), libsqlite3.so.0 (Django ORM), libz.so.1
(zlib, required transitively via binascii/base64), none of which
are included in distroless/cc-debian12
Verified booting successfully on Unikraft Cloud after fixing two missing
shared library errors (libpython3.12.so.1.0, libz.so.1).
… files from non-distroless version Those are exactly the same files as the ones in httpserver-python3.12-django5.0 directory.
…t file Slightly modified to fit the new distroless version.
Modified to fit new distroless version of python-django http server.
…i-stage Dockerfile First stage install dependencies, the second runs the python scrypt. Use gcr.io/distroless/cc-debian12 instead of python3-debian12 as the runtime base image to resolve ABI compatibility issues with Python 3.12. The python3-debian12 image provides Python 3.11 and the python3-debian13 image provides Python 3.13 by default, while thebuilder uses Python 3.12 (bookworm), leading to mismatched library paths and import errors for modules like uvicorn. The distroless version was made to keep the 3.12 version of Python. cc-debian12 includes glibc, libssl, libz, and libgcc, providing a more flexible runtime environment for Python's native dependencies. The Python interpreter and its libraries (libpython3.12.so.1.0) are copied from the builder stage, ensuring version consistency.
…on test file Modify to fit the new distroless version.
…ME.md Modify to fit the new distroless version.
…dified files Unmodified files from the non-distroless version: Kraftfile, source file and python test file.
…EDME.md Modify title.
…files Unmodified files from the non-distroless version: Kraftfile, source file, requirements.txt.
Slightly modified to fit new distroless version.
… file Slightly modified to fit new distroless version.
… Dockerfile First stage resolves dependencies, the second one imports libraries from the first stage and runs the server. Use gcr.io/distroless/cc-debian12 instead of python3-debian12 as the runtime base image to resolve ABI compatibility issues with Python 3.12. The python3-debian12 image provides Python 3.11 and python3-debian13 image provides Python 3.13 by default, while the builder uses Python 3.12 (bookworm), leading to mismatched library paths and import errors for modules like uvicorn. cc-debian12 includes glibc, libssl, libz, and libgcc, providing a more flexible runtime environment for Python's native dependencies. The Python interpreter and its libraries (libpython3.12.so.1.0) are copied from the builder stage, ensuring version consistency.
…dified files Unmodified files from non-distroless version: source file, Kraftfile, init db scrypt, requirements.txt, schema.sql, static and templates directories.
…kerignore Non-distroless version's identic .dockerfile.
…ME.md Slightly modified to fit new distroless version.
…on test file Slightly modified from non-distroless version to fit new distroless version.
…i-stage Dockerfile First stage installs dependencies, second stage gets the libraries and runs the server. Use gcr.io/distroless/cc-debian12 instead of python3-debian12 as the runtime base image to resolve ABI compatibility issues with Python 3.12. The python3-debian12 image provides Python 3.11 and python3-debian13 image provides Python 3.13 by default, while the builder uses Python 3.12 (bookworm), leading to mismatched library paths and import errors for modules like uvicorn. cc-debian12 includes glibc, libssl, libz, and libgcc, providing a more flexible runtime environment for Python's native dependencies. The Python interpreter and its libraries (libpython3.12.so.1.0) are copied from the builder stage, ensuring version consistency.
c5c8861 to
9ab88c8
Compare
|
Fixed the typos. The first observation is wrong and I concluded that is wrong by testing the example and seeing it function exactly like it's original non-distroless version. There was also a recommandation to modify a README but the section that had to be modified wasn't directly about the distroless example, but for using pip, and it's copied from another non-distroless example. About modifying the other files that weren't part of the shift from scratch to distroless (like dockerfile and kraftfile), I assume that the previous version that was used like a template has it's source code and other config files correct and optimal. All the examples work when tested manually. |
There was a problem hiding this comment.
🟡 Changes recommended
Critical security, startup, persistence, and runtime-version issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (14)
httpserver-python3.12-distroless/Dockerfile:1
- This image is the Debian 13
python3runtime (Python 3.13), while the example directory, test, and README identify it as Python 3.12. The published example therefore does not provide the advertised interpreter version; use a Python 3.12 runtime or rename/update all of the example's versioned references.
FROM gcr.io/distroless/python3-debian13:nonroot
httpserver-python3.12-distroless/README.md:193
- This entire section is copied from the Flask example: it links to Flask, builds
httpserver-python312-flask30, and assumes arequirements.txtthat this project does not contain. Following it deploys a different example, so replace it with Python-specific guidance or remove it.
## Using `pip`
httpserver-python3.12-django5.0-distroless/README.md:141
- There is no
Dockerfile.distrolessin this example; the file is namedDockerfile. As written, the customization instructions point readers to a nonexistent file.
You can set the username and password in the `Dockerfile.distroless` (more on this file later) to `unikraft/unikraft`.
httpserver-python3.12-django5.0-distroless/README.md:145
- The warning acknowledges the insecure settings but still presents a public deployment path immediately above it, including a known admin password. This example should not be advertised as a reachable Cloud deployment until the fixed key/debug mode/default credential are removed or clearly isolated to a local-only setup.
> The example sets ALLOWED_HOSTS to * and runs in debug mode.
> For a production site, ensure you read the recommended deployment guides of the official Django project.
httpserver-python3.12-django5.0-distroless/helloworld/settings.py:28
- Trusting
https://*.unikraft.appaccepts CSRF origins from every Unikraft app subdomain, not just this instance. A page hosted by another tenant could submit authenticated admin requests to this app. Configure the exact generated service origin at deployment instead of a global wildcard.
CSRF_TRUSTED_ORIGINS = ["https://*.unikraft.app"]
httpserver-python3.12-fastapi-0.121.3-distroless/requirements.txt:1
- The example is explicitly named
fastapi-0.121.3, but this unconstrained requirement installs whatever FastAPI is latest at build time, making the documented version and image behavior non-reproducible. Pin FastAPI (and the Uvicorn dependency) to the versions this example targets.
fastapi[standard]
httpserver-python3.12-flask3.0-distroless/README.md:182
- The Dockerfile copies the application to
/src/server.pyand the Kraftfile runs that path, but this explanation says it is copied to/app/server.py. Following the documented path would leave the runtime instructions inconsistent with the image.
* `COPY ./server.py /app/server.py`: Copy the server implementation file (`server.py`) in the Docker filesystem (in `/app/server.py`).
httpserver-python3.12-flask3.0-sqlite-distroless/README.md:225
- The app's directory is
templates/(and the implementation lists that same path above), but this customization instruction usestemplate/. Users following it will look in or edit the wrong directory.
httpserver-python3.12-flask3.0-sqlite-distroless/README.md:113 - The sample instance address shown above is
lingering-orangutan-840mmdvd.fra.unikraft.app, but this command uses the unrelatedyoung-night-5fpf0jj8address. Copying the documented command therefore cannot query the instance just deployed.
httpserver-python3.12-flask3.0-sqlite-distroless/README.md:180 - The sentence uses
initializedwhere the infinitiveinitializeis required.
httpserver-python3.12-flask3.0-sqlite-distroless/README.md:48 - The database is built at
/app/database.dband the app exposes INSERT/UPDATE/DELETE routes, but this deployment uses an EROFS rootfs and does not request stateful storage or mount a writable volume. Mutations therefore cannot be reliably written or preserved across restarts. Add the appropriate stateful/volume configuration to both CLI examples, or move the database to a writable volume.
httpserver-python3.12-flask3.0-sqlite-distroless/templates/base.html:9 - The added
static/css/style.cssis never linked from this base template, so the app's own stylesheet (including theh1rules) is never served. Add aurl_for('static', filename='css/style.css')stylesheet link in the head.
httpserver-python3.12-flask3.0-sqlite-distroless/templates/create.html:11 - The form labels use
for="title"/for="content", but neither control has the correspondingid; the same mismatch also exists in the edit form. Screen readers and label-based focus cannot associate these controls, so add matchingidattributes to the input and textarea.
httpserver-python3.12-flask3.0-sqlite-distroless/templates/edit.html:11 - The edit form labels use
for="title"/for="content", but neither control has the correspondingid; the same mismatch exists in create.html. Screen readers and label-based focus cannot associate these controls, so add matchingidattributes to the input and textarea.
- Files reviewed: 50/51 changed files
- Comments generated: 8
- Review effort level: Lite
| RUN python3 manage.py migrate && \ | ||
| DJANGO_SUPERUSER_PASSWORD=unikraft python3 manage.py createsuperuser --noinput --username unikraft --email mail@example.com |
|
|
||
| rootfs: | ||
| source: ./Dockerfile | ||
| format: erofs |
| Use `curl` to query the Unikraft Cloud instance of the Django web app server: | ||
|
|
||
| ```bash | ||
| curl https://dawn-sound-n5wrkxi2.fra.unikraft.app |
| SECRET_KEY = "django-insecure-gb8wk^txj5ag6)q_wh82@i0nwhw(jhc!ga7!ux#yz^0hsb@=9g" | ||
|
|
||
| # SECURITY WARNING: don't run with debug turned on in production! | ||
| DEBUG = True |
| source: ./Dockerfile | ||
| format: erofs | ||
|
|
||
| cmd: ["/usr/bin/python3", "-m", "uvicorn", "src.server:app", "--host", "0.0.0.0", "--port", "8080"] |
|
|
||
| rootfs: | ||
| source: ./Dockerfile | ||
| format: erofs |
| conn.execute('DELETE FROM posts WHERE id = ?', (id,)) | ||
| conn.commit() | ||
| conn.close() | ||
| flash('"{}" was successfully deleted!'.format(post['title'])) |
| `source: ./Dockerfile` means the filesystem is built using the `Dockerfile`. | ||
| `format: erofs` means the filesystem type is [EROFS](https://erofs.docs.kernel.org/). | ||
|
|
||
| * `cmd: ["/usr/bin/python3", "-m", "uvicorn", "src.server:app", "--host", "0.0.0.0", "--port", "8080"]`: Use this as the starting command of the instance. |
Here are http servers made with Python or Ruby - their distroless versions. Details in the commit messages.