Distroless builds - rust http servers - #452
Conversation
The files that should not be sent to docker daemon.
Same Cargo.lock as for the non-distroless version
Same Cargo.toml as for the non-distroless version
Same Kraftfile as for the non-distroless version
Almost same README as for the non-distroless version. Just modified the names and command examples.
Same src as for the non-distroless version
Almost same test file as for the non-distroless version. Just modified the names so it fits the new context.
…erfile First stage compiles/builds the static executable, the second imports a lightweight distroless image for static executables and languages like Rust or Go and gets the executable from the builder stage and runs it. I think that it's worth mentioning that if the user wants to extend the capabilities of the server he/she has to add CA certificates via Dockerfile to the second stage so the server can communicate with other servers or DBs. If the reviewer also considers that this is worth mentioning in the README, tell me so I can modify the README. Considering that it is just an example, some may consider that it should just work and be as light-weight as possible so CA certs are not necessary.
Add multi-stage dockerfile that builds static executable (first stage) and runs it using a distroless image for rust and go executables.
Add list of files to be ignored by docker daemon.
Kept the old .gitignore file.
Slightly modified the old (non-distroless version) README.md.
Slightly modified the non-ditroless test file for rust1.91 http server.
Add the non-distroless Kraftfile as this variant do not require modifications to Kraftfile.
Kept the old non-distroless variant of server.rs.
Forgot to add "distroless" in some places.
Add multi-stage dockerfile. First stage builds static executable. Second stage runs it with distroless image of debian13 for rust and go static executables.
Instead of py_file-distroless.py it was py_file.py
Files to be ignored by docker daemon.
Kept the old .gitignore.
Kept the old Cargo.lock
Kept the old version on Cargo.toml
Kept the old file from the non-distroless version.
Kept the old version.
Slightly modified the old version's by adding "-distroless" in some places and some new examples for running and listing unikraft instances.
Kept the old source file.
…file Slightly modified the test file by adding "-distroless" in some places.
… dockerfile Add multi-stage dockerfile to build and run (first and second stage) the rust http server.
Files to be ignored by docker daemon.
Kept the .gitignore from the original version.
Kept the old Cargo.lock file.
Kept the old Cargo.toml file.
Kept old Kraftfile from non-distroless version.
Slightly modified the previous version adding some command examples and "-distroless".
Kept the original source file.
… file Slightly modified test file to fit to the new distroless example.
Slightly modify previous versions's by changing entry-point to fit with new distroless image.
Kept old .gitignore
Slightly modify old README to fit to distroless version.
…ootfs, shared_lib directories The same as in non-distroless variant.
… file Slightly modify to fit to this version.
… Dockerfile First stage builds the executable and the second one runs it. Didn't use a Google distroless image. I used the distrolessdevops/nginx-distroless:1.25.3 image because it was the same as the one used in the second stage of the non-distroless Dockerfile, so it had perfect compatibility with the built executable. I have also used the digest of the image to get the working variant even if it gets modified by the publisher. I have also taken into consideration using the Chainguard variant but I could have only imported the latest version for free (not a fix one, or a previous one), so I couldn't know if the image would still work after being updated.
Eliminate unnecessary docker commands like EXPOSE, CMD and ENTRYPOINT from those dockerfiles. The commands are written already in Kraftfile.
There was a problem hiding this comment.
🟡 Changes recommended
There are confirmed functional correctness issues (e.g., a non-compiling Rust server thread spawn and an incorrect rootfs copy path) plus a few packaging/docs mistakes that should be fixed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR introduces multiple Rust-based HTTP server examples packaged as distroless images for Unikraft Cloud deployments, along with end-to-end tests and deployment instructions.
Changes:
- Added new distroless Rust HTTP server examples (plain Rust, Tokio, Actix Web, Rocket, and a Trunk/Leptos static site served via Nginx).
- Added Kraftfile + Dockerfile build definitions and README deployment guides for each example.
- Added end-to-end Python tests that build, deploy, and verify responses from each deployed service.
File summaries
| File | Description |
|---|---|
| httpserver-rust1.91-distroless/test_httpserver-rust1.91-distroless.py | E2E test that deploys the example and asserts “Hello, World!” |
| httpserver-rust1.91-distroless/server.rs | Minimal HTTP server implementation for the example |
| httpserver-rust1.91-distroless/README.md | Deployment/run instructions and expected outputs |
| httpserver-rust1.91-distroless/Kraftfile | Unikraft Cloud build/run spec for the example |
| httpserver-rust1.91-distroless/Dockerfile | Distroless build pipeline for the example binary |
| httpserver-rust1.91-distroless/.gitignore | Ignores build artifacts |
| httpserver-rust1.91-distroless/.dockerignore | Reduces Docker build context for the example |
| httpserver-rust1.88-rocket0.5-distroless/test_httpserver-rust1.88-rocket0.5-distroless.py | E2E test that hits a Rocket route and validates response |
| httpserver-rust1.88-rocket0.5-distroless/src/main.rs | Rocket-based HTTP server implementation |
| httpserver-rust1.88-rocket0.5-distroless/Rocket.toml | Rocket bind address/port config |
| httpserver-rust1.88-rocket0.5-distroless/README.md | Deployment/run instructions for the Rocket example |
| httpserver-rust1.88-rocket0.5-distroless/Kraftfile | Unikraft Cloud build/run spec for Rocket example |
| httpserver-rust1.88-rocket0.5-distroless/Dockerfile | Distroless build pipeline for Rocket binary |
| httpserver-rust1.88-rocket0.5-distroless/Cargo.toml | Rocket example crate manifest |
| httpserver-rust1.88-rocket0.5-distroless/Cargo.lock | Rocket example dependency lockfile |
| httpserver-rust1.88-rocket0.5-distroless/.gitignore | Ignores Cargo build artifacts |
| httpserver-rust1.88-rocket0.5-distroless/.dockerignore | Reduces Docker build context for Rocket example |
| httpserver-rust1.88-actix-web4-distroless/test_httpserver-rust1.88-actix-web4-distroless.py | E2E test for Actix endpoints (/ and /hey) |
| httpserver-rust1.88-actix-web4-distroless/src/main.rs | Actix Web HTTP server implementation |
| httpserver-rust1.88-actix-web4-distroless/README.md | Deployment/run instructions for the Actix example |
| httpserver-rust1.88-actix-web4-distroless/Kraftfile | Unikraft Cloud build/run spec for Actix example |
| httpserver-rust1.88-actix-web4-distroless/Dockerfile | Distroless build pipeline for Actix binary |
| httpserver-rust1.88-actix-web4-distroless/Cargo.toml | Actix example crate manifest |
| httpserver-rust1.88-actix-web4-distroless/Cargo.lock | Actix example dependency lockfile |
| httpserver-rust1.88-actix-web4-distroless/.gitignore | Ignores Cargo build artifacts |
| httpserver-rust1.88-actix-web4-distroless/.dockerignore | Reduces Docker build context for Actix example |
| httpserver-rust1.75-tokio-distroless/test_httpserver-rust1.75-tokio-distroless.py | E2E test for Tokio-based server example |
| httpserver-rust1.75-tokio-distroless/src/main.rs | Tokio-based TCP HTTP response loop |
| httpserver-rust1.75-tokio-distroless/README.md | Deployment/run instructions for Tokio example |
| httpserver-rust1.75-tokio-distroless/Kraftfile | Unikraft Cloud build/run spec for Tokio example |
| httpserver-rust1.75-tokio-distroless/Dockerfile | Distroless build pipeline for Tokio binary |
| httpserver-rust1.75-tokio-distroless/Cargo.toml | Tokio example crate manifest |
| httpserver-rust1.75-tokio-distroless/Cargo.lock | Tokio example dependency lockfile |
| httpserver-rust1.75-tokio-distroless/.dockerignore | Reduces Docker build context for Tokio example |
| httpserver-rust-trunkrs-leptos-distroless/test_httpserver-rust-trunkrs-leptos-distroless.py | E2E test that verifies a 200 response from the deployed service |
| httpserver-rust-trunkrs-leptos-distroless/shared_lib/src/lib.rs | Shared library used by the frontend (with unit tests) |
| httpserver-rust-trunkrs-leptos-distroless/shared_lib/Cargo.toml | Shared library crate manifest |
| httpserver-rust-trunkrs-leptos-distroless/shared_lib/Cargo.lock | Shared library lockfile |
| httpserver-rust-trunkrs-leptos-distroless/rootfs/rootfs/wwwroot/index.html | Static web root content included in the image rootfs |
| httpserver-rust-trunkrs-leptos-distroless/rootfs/rootfs/etc/nginx/nginx.conf | Nginx configuration included in the image rootfs |
| httpserver-rust-trunkrs-leptos-distroless/README.md | Deployment/run instructions for Trunk/Leptos example |
| httpserver-rust-trunkrs-leptos-distroless/Kraftfile | Unikraft Cloud build/run spec for Nginx-based example |
| httpserver-rust-trunkrs-leptos-distroless/frontend/Trunk.toml | Trunk build configuration |
| httpserver-rust-trunkrs-leptos-distroless/frontend/src/main.rs | Leptos CSR frontend code |
| httpserver-rust-trunkrs-leptos-distroless/frontend/nginx.conf | Nginx config used/overlaid for serving the built frontend |
| httpserver-rust-trunkrs-leptos-distroless/frontend/index.html | Trunk entry HTML |
| httpserver-rust-trunkrs-leptos-distroless/frontend/Cargo.toml | Frontend crate manifest |
| httpserver-rust-trunkrs-leptos-distroless/Dockerfile | Builds frontend assets with Trunk and packages into distroless Nginx image |
| httpserver-rust-trunkrs-leptos-distroless/.gitignore | Ignores build artifacts |
Review details
Suppressed comments (3)
httpserver-rust1.91-distroless/server.rs:21
stream.write(response)may perform a partial write, which can result in truncated HTTP responses. Usewrite_all()(and ideally includeContent-Length/Connection: close) to ensure the full response is sent.
httpserver-rust1.91-distroless/server.rs:40- The spawned thread closure needs to capture
streamby value (move) to satisfy the'staticbound, andJoinHandleis#[must_use]so dropping it without binding may trigger warnings. Capture withmoveand explicitly discard the handle.
httpserver-rust1.88-actix-web4-distroless/README.md:103 - Typo in the sample legacy output: service name
loral-water-sndspsy3should match the domain shown above (floral-water-sndspsy3).
- Files reviewed: 45/50 changed files
- Comments generated: 8
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| #FROM distrolessdevops/nginx-distroless:1.25.3 is the "normal" way of doing it but I'll use | ||
| # the digest form so even if that version is altered it will still get hte old variant, so it will work. | ||
|
|
||
| COPY ./rootfs/ / |
| view! { | ||
| <p> | ||
| <div> | ||
| <button | ||
| on:click=move |_| set_count.set(shared_lib::add(count.get(), 1)) | ||
| > | ||
| "Click to add one" | ||
| </button> | ||
| </div> | ||
| <div> | ||
| <button | ||
| on:click=move |_| set_count.set(shared_lib::subtract(count.get(), 1)) | ||
| > | ||
| "Click to remove one" | ||
| </button> | ||
| </div> | ||
| <div> | ||
| <span>Count: {count}</span> | ||
| </div> | ||
| </p> | ||
| } |
| fn handle_read(mut stream: &TcpStream) { | ||
| let mut buf = [0u8 ;4096]; | ||
| match stream.read(&mut buf) { | ||
| Ok(_) => { | ||
| let req_str = String::from_utf8_lossy(&buf); | ||
| println!("{}", req_str); | ||
| }, | ||
| Err(e) => println!("Unable to read stream: {}", e), | ||
| } | ||
| } |
|
|
||
| **Using the unikraft CLI (Recommended)** | ||
| ```bash title="unikraft" | ||
| unikraft build . --output <my-org>/httpserver-rust188-actix-web4-dsitroless:latest |
| @@ -0,0 +1,3 @@ | |||
| Kraftfile | |||
| README.md | |||
| test_httpserver-rust1.91.py | |||
|
|
||
| ```ansi title="unikraft" | ||
| METRO NAME STATE IMAGE ARGS MEMORY VCPUS FQDN CREATED | ||
| fra httpserver-rust191-dsitroless-pinzf standby <my-org>/httpserver-rust191-dsitroless 384MiB 1 snowy-feather-k4pfgl8t.fra.unikraft.app 2 minutes ago |
| use std::net::{TcpStream, TcpListener}; | ||
| use std::io::{Read, Write}; | ||
| use std::thread; |
Fix the issues that Copilot found and I considered them actual mistakes.
728f5f3 to
40c0013
Compare
|
I have fixed the typos. I do not consider the COPY rootfs "mistake" detected by Copilot as an actual problem. The previous non-distroless dockerfile did the same thing and worked just well. Same for the distroless version. |
Created distroless build for rust http server that uses tokio. Details in commit messages. Needs review.