From 84701790770e329808627e44dedff81bc73c308e Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 12:01:02 +0300 Subject: [PATCH 01/28] feat: Add httpserver-ruby3.2-distroless - Add Kraftfile Kept the non-distroless variant's Kraftfile. --- httpserver-ruby3.2-distroless/Kraftfile | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 httpserver-ruby3.2-distroless/Kraftfile diff --git a/httpserver-ruby3.2-distroless/Kraftfile b/httpserver-ruby3.2-distroless/Kraftfile new file mode 100644 index 00000000..7a485934 --- /dev/null +++ b/httpserver-ruby3.2-distroless/Kraftfile @@ -0,0 +1,12 @@ +spec: v0.7 + +runtime: base-compat:latest + +targets: + - kraftcloud/x86_64 + +rootfs: + source: ./Dockerfile + format: erofs + +cmd: ["/usr/bin/ruby", "/src/server.rb"] From 8e1aaccbb1015f5923e7bb8d3e242d9fedc85857 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 12:02:21 +0300 Subject: [PATCH 02/28] feat: Add httpserver-ruby3.2-distroless - Add source file Kept the same source file. --- httpserver-ruby3.2-distroless/server.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 httpserver-ruby3.2-distroless/server.rb diff --git a/httpserver-ruby3.2-distroless/server.rb b/httpserver-ruby3.2-distroless/server.rb new file mode 100644 index 00000000..76553fa6 --- /dev/null +++ b/httpserver-ruby3.2-distroless/server.rb @@ -0,0 +1,20 @@ +# https://dev.to/leandronsp/web-basics-a-simple-http-server-in-ruby-2jj4 + +require 'socket' + +socket = TCPServer.new(8080) + +loop do + client = socket.accept + + request = client.gets + + response = "HTTP/1.1 200 OK\r\n" \ + "Content-type: text/html\r\n" \ + "Connection: close\r\n" \ + "\r\n" \ + "Hello, World!" + client.puts(response) + + client.close +end From ff5b82e7633ed87fe9d7295f500c9da3982ff39e Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 12:03:16 +0300 Subject: [PATCH 03/28] feat: Add httpserver-ruby3.2-distroless - Add python test file Slightly modify to fit the distroless version. --- .../test_httpserver-ruby3.2-distroless.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 httpserver-ruby3.2-distroless/test_httpserver-ruby3.2-distroless.py diff --git a/httpserver-ruby3.2-distroless/test_httpserver-ruby3.2-distroless.py b/httpserver-ruby3.2-distroless/test_httpserver-ruby3.2-distroless.py new file mode 100644 index 00000000..c8d2ec6d --- /dev/null +++ b/httpserver-ruby3.2-distroless/test_httpserver-ruby3.2-distroless.py @@ -0,0 +1,31 @@ +"""End-to-end test for the ``httpserver-ruby3.2-distroless`` example. + +Mirrors the manual steps from ``httpserver-ruby3.2-distroless/README.md``: + +1. ``unikraft build . --output /httpserver-ruby32-distroless:`` +2. ``unikraft run --metro -p 443:8080/tls+http -m 256M --image ...`` +3. ``curl https://`` and assert "Hello, World!". +""" + +from __future__ import annotations + +from _testlib.unikraft import extract_instance_name, extract_instance_url + + +def test_httpserver_ruby32_serves_hello(build_image, run_instance, http, wait_instance): + image = build_image("httpserver-ruby3.2-distroless", "httpserver-ruby3.2-distroless") + + instance = run_instance( + image, + publish=["443:8080/tls+http"], + memory="256M", + ) + + url = extract_instance_url(instance) + assert url, f"could not determine instance URL from: {instance!r}" + + wait_instance(extract_instance_name(instance), "running") + + resp = http(url) + assert resp.status_code == 200 + assert "Hello, World!" in resp.text From 6d6bfb9b2bcf5c593ad6de4e47034402c3c61b1e Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 12:05:02 +0300 Subject: [PATCH 04/28] feat: Add httpserver-ruby3.2-distroless - Add README.md Slightly modify to fit new distroless version. --- httpserver-ruby3.2-distroless/README.md | 190 ++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 httpserver-ruby3.2-distroless/README.md diff --git a/httpserver-ruby3.2-distroless/README.md b/httpserver-ruby3.2-distroless/README.md new file mode 100644 index 00000000..83432c9d --- /dev/null +++ b/httpserver-ruby3.2-distroless/README.md @@ -0,0 +1,190 @@ +# Distroless Ruby HTTP Server + +This guide explains how to create and deploy a simple Ruby-based HTTP web server. +To run this example, follow these steps: + +1. Install the CLI. + Use the [unikraft CLI](https://unikraft.com/docs/cli/unikraft) or the legacy [kraft CLI](https://unikraft.org/docs/cli/install). + You need a [BuildKit](https://github.com/moby/buildkit) builder. The easiest way to get one is via [Docker](https://docs.docker.com/engine/install/). + Alternatively, you can also directly set up and use BuildKit, see the [quick start](https://github.com/moby/buildkit#quick-start). + + > **Note**: + > The unikraft CLI is the current standard, while kraft is the legacy version. + > Choose one of the CLIs below and only run the commands associated with it for the rest of this guide. + +2. Clone the [`examples` repository](https://github.com/unikraft-cloud/examples) and `cd` into the `examples/httpserver-ruby3.2-distroless/` directory: + + ```bash + git clone https://github.com/unikraft-cloud/examples + cd examples/httpserver-ruby3.2-distroless/ + ``` + +Make sure to log into Unikraft Cloud and pick a [metro](https://unikraft.com/docs/platform/metros) close to you. +This guide uses `fra` (Frankfurt, πŸ‡©πŸ‡ͺ): + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft login +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +# Set Unikraft Cloud access token +export UKC_TOKEN=token +# Set metro to Frankfurt, DE +export UKC_METRO=fra +``` + +When done, invoke the following command to deploy this app on Unikraft Cloud: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft build . --output /httpserver-ruby32-distroless:latest +unikraft run --metro fra \ + -m 256M \ + -p 443:8080/tls+http \ + --scale-to-zero policy=on,cooldown-time=1000 \ + --image /httpserver-ruby32-distroless:latest +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud deploy \ + -M 256Mi \ + -p 443:8080/tls+http \ + --scale-to-zero on \ + --scale-to-zero-cooldown 1s \ + . +``` + +The output shows the instance address and other details: + +**Using the unikraft CLI (Recommended)** +```ansi title="unikraft" +metro: fra +name: httpserver-ruby32-distroless-s6l8n +uuid: b1ebbbc0-5efa-476c-adb6-99866773245c +state: starting +image: /httpserver-ruby32-distroless +resources: + memory: 256MiB + vcpus: 1 +service: + uuid: bcc07c7c-9289-11f4-6e3d-8f7fdde45256 + name: silent-resonance-1jtz5c66 + domains: + - fqdn: silent-resonance-1jtz5c66.fra.unikraft.app +networks: +- uuid: 50203783-cf51-81d1-aa2d-615f939043af + private-ip: 10.0.3.3 + mac: 12:b0:0a:4f:7a:84 +timestamps: + created: just now +``` + +or + +**Using the legacy kraft CLI** +```ansi title="kraft" +[●] Deployed successfully! + β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ name: httpserver-ruby32-distroless-s6l8n + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ uuid: b1ebbbc0-5efa-476c-adb6-99866773245c + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ metro: https://api.fra.unikraft.cloud/v1 + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ state: starting + β”œβ”€β”€β”€β”€β”€β”€β”€ domain: https://silent-resonance-1jtz5c66.fra.unikraft.app + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ image: oci://unikraft.io//httpserver-ruby32-distroless@sha256:4cf3b341898e6ebff18ff2b68353ef872dded650c9d16a6f005a8fbe8a7cbb3d + β”œβ”€β”€β”€β”€β”€β”€β”€ memory: 256 MiB + β”œβ”€β”€β”€β”€β”€β”€ service: silent-resonance-1jtz5c66 + β”œβ”€ private fqdn: httpserver-ruby32-distroless-s6l8n.internal + └─── private ip: 10.0.3.3 +``` + +In this case, the instance name is `httpserver-ruby32-distroless-s6l8n` and the address is `https://silent-resonance-1jtz5c66.fra.unikraft.app`. +They're different for each run. + +Use `curl` to query the Unikraft Cloud instance of the Ruby-based HTTP web server: + +```bash +curl https://silent-resonance-1jtz5c66.fra.unikraft.app +``` + +```text +Hello, World! +``` + +You can list information about the instance by running: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft instances list +``` + +```ansi title="unikraft" +METRO NAME STATE IMAGE ARGS MEMORY VCPUS FQDN CREATED +fra httpserver-ruby32-distroless-s6l8n running /httpserver-ruby32-distroless 256MiB 1 silent-resonance-1jtz5c66.fra.unikraf… 2 minutes ago +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud instance list +``` + +```ansi title="kraft" +NAME FQDN STATE STATUS IMAGE MEMORY VCPUS ARGS BOOT TIME +httpserver-ruby32-distroless-s6l8n silent-resonance-1jtz5c66.fra.unikraft.app running 12 minutes ago oci://unikraft.io//httpserver-ruby32-distroless@sha256:... 256 MiB 1 71.19 ms +``` + +When done, you can remove the instance: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft instances delete httpserver-ruby32-distroless-s6l8n +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud instance remove httpserver-ruby32-distroless-s6l8n +``` + +## Customize your app + +To customize the app, update the files in the repository, listed below: + +* `server.rb`: the actual Ruby HTTP server +* `Kraftfile`: the Unikraft Cloud specification +* `Dockerfile`: the Docker-specified app filesystem + +The following options are available for customizing the app: + +* If you only update the implementation in the `server.rb` source file, you don't need to make any other changes. + +* If you create any new source files, copy them into the app filesystem by using the `COPY` command in the `Dockerfile`. + +* More extensive changes may require extending the `Dockerfile` ([see `Dockerfile` syntax reference](https://docs.docker.com/engine/reference/builder/)). + +## Learn more + +Use the `--help` option for detailed information on using Unikraft Cloud: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft --help +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud --help +``` + +Or visit the [CLI Reference](https://unikraft.com/docs/cli/unikraft) or the [legacy CLI Reference](https://unikraft.com/docs/cli/kraft/overview). From e2348aa03c1a00ddd200d538683b4750aff5a4c6 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 12:06:53 +0300 Subject: [PATCH 05/28] feat: Add httpserver-ruby3.2-distroless - Add multi-stage Dockerfile 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. --- httpserver-ruby3.2-distroless/Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 httpserver-ruby3.2-distroless/Dockerfile diff --git a/httpserver-ruby3.2-distroless/Dockerfile b/httpserver-ruby3.2-distroless/Dockerfile new file mode 100644 index 00000000..5b9465b6 --- /dev/null +++ b/httpserver-ruby3.2-distroless/Dockerfile @@ -0,0 +1,10 @@ +FROM ruby:3.2.2-bookworm AS builder + +WORKDIR /src + +COPY server.rb /src/server.rb + +# FROM openeuler/distroless-ruby:3.2.2-oe2403lts - uncomment this line and delete the next one if you want updated versions +FROM openeuler/distroless-ruby@sha256:531edf8008868f7dac447e6dc9ffd4576968b93e2169f55196dfab003a377e61 + +COPY --from=builder /src/server.rb /src/server.rb From 9abd7ae6e9549c68a921f10d9440ad9e56e20ee6 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 21:14:57 +0300 Subject: [PATCH 06/28] feat: Add httpserver-python3.12-distroless - Add Kraftfile Kept Kraftfile from non-distroless version. --- httpserver-python3.12-distroless/Kraftfile | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 httpserver-python3.12-distroless/Kraftfile diff --git a/httpserver-python3.12-distroless/Kraftfile b/httpserver-python3.12-distroless/Kraftfile new file mode 100644 index 00000000..54ccf7ea --- /dev/null +++ b/httpserver-python3.12-distroless/Kraftfile @@ -0,0 +1,12 @@ +spec: v0.7 + +runtime: base-compat:latest + +targets: + - kraftcloud/x86_64 + +rootfs: + source: ./Dockerfile + format: erofs + +cmd: ["/usr/bin/python3", "/src/server.py"] From 0010807121698fa5050050fbe6c166fe3c58fce2 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 21:16:09 +0300 Subject: [PATCH 07/28] feat: Add httpserver-python3.12-distroless - Add python test file Slightly modified non-distroless version's test file to fit the new version. --- .../test_httpserver-python3.12-distroless.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 httpserver-python3.12-distroless/test_httpserver-python3.12-distroless.py diff --git a/httpserver-python3.12-distroless/test_httpserver-python3.12-distroless.py b/httpserver-python3.12-distroless/test_httpserver-python3.12-distroless.py new file mode 100644 index 00000000..486cbef5 --- /dev/null +++ b/httpserver-python3.12-distroless/test_httpserver-python3.12-distroless.py @@ -0,0 +1,31 @@ +"""End-to-end test for the ``httpserver-python3.12-distroless`` example. + +Mirrors the manual steps from ``httpserver-python3.12-distroless/README.md``: + +1. ``unikraft build . --output /httpserver-python3.12-distroless:`` +2. ``unikraft run --metro -p 443:8080/tls+http -m 512M --image ...`` +3. ``curl https://`` and assert the default Python welcome page. +""" + +from __future__ import annotations + +from _testlib.unikraft import extract_instance_name, extract_instance_url + + +def test_python_serves_welcome_message(build_image, run_instance, http, wait_instance): + image = build_image("httpserver-python3.12-distroless", "httpserver-python3.12-distroless") + + instance = run_instance( + image, + publish=["443:8080/tls+http"], + memory="512M", + ) + + url = extract_instance_url(instance) + assert url, f"could not determine instance URL from: {instance!r}" + + wait_instance(extract_instance_name(instance), "running") + + resp = http(url) + assert resp.status_code == 200 + assert "Hello, World!" in resp.text From c44f129aedfc284b7baddcffc464dc2ef0c58059 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 21:17:39 +0300 Subject: [PATCH 08/28] feat: Add httpserver-python3.12-distroless - Add source file Kept source file from non-distroless version. --- httpserver-python3.12-distroless/server.py | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100755 httpserver-python3.12-distroless/server.py diff --git a/httpserver-python3.12-distroless/server.py b/httpserver-python3.12-distroless/server.py new file mode 100755 index 00000000..59fb7d38 --- /dev/null +++ b/httpserver-python3.12-distroless/server.py @@ -0,0 +1,31 @@ +import argparse +from http.server import HTTPServer, BaseHTTPRequestHandler + +class MyServer(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(bytes("Hello, World!\n", "utf-8")) + +def main(args): + server = HTTPServer((args.host, args.port), MyServer) + + print("starting server at %s:%s" % (args.host, args.port)) + + try: + server.serve_forever() + + except KeyboardInterrupt: + pass + + print("server stopped") + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--host", type=str, default="0.0.0.0") + parser.add_argument("--port", type=int, default=8080) + return parser.parse_args() + +if __name__ == "__main__": + main(parse_args()) From f46dd99db865b3ff4ff91d70eb770cfef6321641 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 21:21:39 +0300 Subject: [PATCH 09/28] feat: Add httpserver-python3.12-distroless - Add README.md Modified it to fit hte distroless version. --- httpserver-python3.12-distroless/README.md | 281 +++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 httpserver-python3.12-distroless/README.md diff --git a/httpserver-python3.12-distroless/README.md b/httpserver-python3.12-distroless/README.md new file mode 100644 index 00000000..0b734728 --- /dev/null +++ b/httpserver-python3.12-distroless/README.md @@ -0,0 +1,281 @@ +# Distroless Python HTTP Server + +This guide explains how to create and deploy a simple Python-based HTTP web server. +To run this example, follow these steps: + +1. Install the CLI. + Use the [unikraft CLI](https://unikraft.com/docs/cli/unikraft) or the legacy [kraft CLI](https://unikraft.org/docs/cli/install). + You need a [BuildKit](https://github.com/moby/buildkit) builder. The easiest way to get one is via [Docker](https://docs.docker.com/engine/install/). + Alternatively, you can also directly set up and use BuildKit, see the [quick start](https://github.com/moby/buildkit#quick-start). + + > **Note**: + > The unikraft CLI is the current standard, while kraft is the legacy version. + > Choose one of the CLIs below and only run the commands associated with it for the rest of this guide. + +2. Clone the [`examples` repository](https://github.com/unikraft-cloud/examples) and `cd` into the `examples/httpserver-python3.12-distroless/` directory: + + ```bash + git clone https://github.com/unikraft-cloud/examples + cd examples/httpserver-python3.12-distroless/ + ``` + +Make sure to log into Unikraft Cloud and pick a [metro](https://unikraft.com/docs/platform/metros) close to you. +This guide uses `fra` (Frankfurt, πŸ‡©πŸ‡ͺ): + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft login +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +# Set Unikraft Cloud access token +export UKC_TOKEN=token +# Set metro to Frankfurt, DE +export UKC_METRO=fra +``` + +When done, invoke the following command to deploy this app on Unikraft Cloud: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft build . --output /httpserver-python312-distroless:latest +unikraft run --metro fra \ + -m 512M \ + -p 443:8080/tls+http \ + --scale-to-zero policy=on,cooldown-time=1000 \ + --image /httpserver-python312-distroless:latest +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud deploy \ + -M 512Mi \ + -p 443:8080/tls+http \ + --scale-to-zero on \ + --scale-to-zero-cooldown 1s \ + . +``` + +The output shows the instance address and other details: + +**Using the unikraft CLI (Recommended)** +```ansi title="unikraft" +metro: fra +name: httpserver-python312-distroless-ma2i9 +uuid: e7389eee-9808-4152-b2ec-1f3c0541fd05 +state: starting +image: /httpserver-python312-distroless +resources: + memory: 512MiB + vcpus: 1 +service: + uuid: 51a41f63-7e88-c443-b9bf-83cd7c04d975 + name: young-night-5fpf0jj8 + domains: + - fqdn: young-night-5fpf0jj8.fra.unikraft.app +networks: +- uuid: 53da3490-c6f5-3718-25f1-219a65163c73 + private-ip: 10.0.3.3 + mac: 12:b0:18:0c:cb:aa +timestamps: + created: just now +``` + +or + +**Using the legacy kraft CLI** +```ansi title="kraft" +[●] Deployed successfully! + β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ name: httpserver-python312-distroless-ma2i9 + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ uuid: e7389eee-9808-4152-b2ec-1f3c0541fd05 + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ metro: https://api.fra.unikraft.cloud/v1 + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ state: starting + β”œβ”€β”€β”€β”€β”€β”€β”€ domain: https://young-night-5fpf0jj8.fra.unikraft.app + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ image: oci://unikraft.io//httpserver-python312-distroless@sha256:278cb8b14f9faf9c2702dddd8bfb6124912d82c11b4a2c6590b6e32fc4049472 + β”œβ”€β”€β”€β”€β”€β”€β”€ memory: 512 MiB + β”œβ”€β”€β”€β”€β”€β”€ service: young-night-5fpf0jj8 + β”œβ”€ private fqdn: httpserver-python312-distroless-ma2i9.internal + └─── private ip: 10.0.3.3 +``` + +In this case, the instance name is `httpserver-python312-distroless-ma2i9` and the address is `https://young-night-5fpf0jj8.fra.unikraft.app`. +They're different for each run. + +Use `curl` to query the Unikraft Cloud instance of the Python-based HTTP web server: + +```bash +curl https://young-night-5fpf0jj8.fra.unikraft.app +``` + +```text +Hello, World! +``` + +You can list information about the instance by running: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft instances list +``` + +```ansi title="unikraft" +METRO NAME STATE IMAGE ARGS MEMORY VCPUS FQDN CREATED +fra httpserver-python312-distroless-ma2i9 running /httpserver-python312-distroless 512MiB 1 young-night-5fpf0jj8.fra.unikraft.app 2 minutes ago +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud instance list +``` + +```ansi title="kraft" +NAME FQDN STATE STATUS IMAGE MEMORY VCPUS ARGS BOOT TIME +httpserver-python312-distroless-ma2i9 young-night-5fpf0jj8.fra.unikraft.app running 1 minute ago oci://unikraft.io//httpserver-python312-distroless@sha256:... 512 MiB 1 15.09 ms +``` + +When done, you can remove the instance: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft instances remove httpserver-python312-distroless-ma2i9 +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud instance remove httpserver-python312-distroless-ma2i9 +``` + +## Customize your app + +To customize the app, update the files in the repository, listed below: + +* `server.py`: the actual Python HTTP server implementation +* `Kraftfile`: the Unikraft Cloud specification +* `Dockerfile`: the Docker-specified app filesystem + +Lines in the `Kraftfile` have the following roles: + +* `spec: v0.7`: The current `Kraftfile` specification version is `0.7`. + +* `runtime: base-compat:latest`: The runtime kernel to use is the base compatibility kernel. + +* `rootfs`: Build the app root filesystem. + `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", "/src/server.py"]`: Use `/usr/bin/python3 /src/server.py` as the starting command of the instance. + +Lines in the `Dockerfile` have the following roles: + +* `FROM gcr.io/distroless/python3-debian13:nonroot`: Build the filesystem from the [`gcr.io/distroless/python3-debian13` container image](https://github.com/GoogleContainerTools/distroless/tree/main/python3), to [create a base image](https://docs.docker.com/build/building/base-images/). + +* `COPY ./server.py /src/server.py`: Copy the server implementation file (`server.py`) in the Docker filesystem (in `/src/server.py`). + +The following options are available for customizing the app: + +* If you only update the implementation in the `server.py` source file, you don't need to make any other changes. + +* If you create any new source files, copy them into the app filesystem by using the `COPY` command in the `Dockerfile`. + +* More extensive changes may require extending the `Dockerfile` ([see `Dockerfile` syntax reference](https://docs.docker.com/engine/reference/builder/)). + This includes the use of Python frameworks and the use of `pip`, as shown in the next section. + +## Using `pip` + +[`pip`](https://pip.pypa.io/en/stable/) is a package manager for Python. +It's used to install dependencies for Python apps. +`pip` uses the `requirements.txt` file to list required dependencies (with versions). + +The [`httpserver-python3.12-flask3.0`](https://github.com/unikraft-cloud/examples/tree/main/httpserver-python3.12-flask3.0) guide details the use of `pip` to deploy an app using the [`Flask`](https://flask.palletsprojects.com/en/3.0.x/) framework on Unikraft Cloud. + +Run the command below to deploy the app on Unikraft Cloud: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft build . --output /httpserver-python312-flask30:latest +unikraft run --metro fra \ + -m 512M \ + -p 443:8080/tls+http \ + --scale-to-zero policy=on,cooldown-time=1000 \ + --image /httpserver-python312-flask30:latest +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud deploy \ + -M 512Mi \ + -p 443:8080/tls+http \ + --scale-to-zero on \ + --scale-to-zero-cooldown 1s \ + . +``` + +Differences from the `httpserver-python3.12-flask3.0` app are also the steps required to create an `pip`-based app: + +1. Add the `requirements.txt` file used by `pip`. + +2. Add framework-specific source files. + In this case, this means the `server.py` file. + +3. Update the `Dockerfile` to: + + 3.1. `COPY` the local files. + + 3.2. `RUN` the `pip3 install` command to install dependencies. + + 3.3. `COPY` of the resulting and required files (`/usr/local/lib/python3.12` and `server.py`) in the app filesystem, using the [`gcr.io/distroless/python3-debian13` container](https://hub.docker.com/_/scratch/). + +The following lists the files: + +The `requirements.txt` file lists the `flask` dependency. + +The `Kraftfile` is the same one used for `httpserver-python3.12-flask3.0`. + +For `Dockerfile` newly added lines have the following roles: + +* `FROM python:3.13-slim-trixie AS builder`: Use the base image of the `python:3.13-slim-trixie` container. + This provides the `pip3` binary and other Python-related components. + Name the current image `builder`. + +* `WORKDIR /src`: Use `/src` as working directory. + All other commands in the `Dockerfile` run inside this directory. + +* `COPY requirements.txt /src`: Copy the package configuration file to the Docker filesystem. + +* `RUN pip3 install ...`: Install `pip` components listed in `requirements.txt`. + +* `COPY --from=builder ...`: Copy generated Python files in the new `builder` image in the `gcr.io/distroless/python3-debian13`-based image. + +Similar actions apply to other `pip3`-based apps. + +See also the [`httpserver-python3.12-django5.0`](https://github.com/unikraft-cloud/examples/tree/main/httpserver-python3.12-django5.0) example. + +## Learn more + +Use the `--help` option for detailed information on using Unikraft Cloud: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft --help +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud --help +``` + +Or visit the [CLI Reference](https://unikraft.com/docs/cli/unikraft) or the [legacy CLI Reference](https://unikraft.com/docs/cli/kraft/overview). From 66f6749cccf6567a283098cbdcfee55d985ee330 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Fri, 28 Aug 2026 21:38:23 +0300 Subject: [PATCH 10/28] feat: Add httpserver-python3.12-distroless - Add Dockerfile 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 --- httpserver-python3.12-distroless/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 httpserver-python3.12-distroless/Dockerfile diff --git a/httpserver-python3.12-distroless/Dockerfile b/httpserver-python3.12-distroless/Dockerfile new file mode 100644 index 00000000..b256b839 --- /dev/null +++ b/httpserver-python3.12-distroless/Dockerfile @@ -0,0 +1,4 @@ +FROM gcr.io/distroless/python3-debian13:nonroot + +WORKDIR /src +COPY server.py /src/server.py From 66d46a8f223b1bd9bfafb6814bc51001e79b8585 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Sat, 29 Aug 2026 17:27:00 +0300 Subject: [PATCH 11/28] feat: Add httpserver-python3.12-django5.0-distroless - Add multi-stage 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). --- .../Dockerfile | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 httpserver-python3.12-django5.0-distroless/Dockerfile diff --git a/httpserver-python3.12-django5.0-distroless/Dockerfile b/httpserver-python3.12-django5.0-distroless/Dockerfile new file mode 100644 index 00000000..f6ac83fe --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/Dockerfile @@ -0,0 +1,45 @@ +FROM python:3.12-bookworm AS build + +WORKDIR /app + +COPY requirements.txt /app + +RUN apt update && \ + apt-get install -y --no-install-suggests --no-install-recommends libev-dev && \ + pip3 install -r requirements.txt --no-cache-dir + +RUN set -xe; \ + /usr/sbin/ldconfig /usr/local/lib + + +FROM build AS static + +ADD . /app + +RUN python3 manage.py collectstatic --noinput + + +FROM build AS db + +ADD . /app + +RUN python3 manage.py migrate && \ + DJANGO_SUPERUSER_PASSWORD=unikraft python3 manage.py createsuperuser --noinput --username unikraft --email mail@example.com + + +FROM gcr.io/distroless/cc-debian12:latest + +WORKDIR /app + +COPY --from=build /usr/local/lib /usr/local/lib +COPY --from=build /usr/local/bin/python3 /usr/bin/python3 +COPY --from=build /usr/local/lib/libpython3.12.so.1.0 /usr/lib/x86_64-linux-gnu/libpython3.12.so.1.0 +COPY --from=build /usr/lib/x86_64-linux-gnu/libev.so.4 /usr/lib/x86_64-linux-gnu/libev.so.4 +COPY --from=build /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/libsqlite3.so.0 +COPY --from=build /usr/lib/x86_64-linux-gnu/libz.so.1 /usr/lib/x86_64-linux-gnu/libz.so.1 + +COPY manage.py main.py /app/ +COPY helloworld /app/helloworld/ + +COPY --from=static /app/static /app/static +COPY --from=db /app/db.sqlite3 /app/db.sqlite3 From 890667006fa654c356b98520c159192db52d499e Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Sat, 29 Aug 2026 17:33:41 +0300 Subject: [PATCH 12/28] frat: Add httpserver-python3.12-django5.0-distroless - Add unmodified files from non-distroless version Those are exactly the same files as the ones in httpserver-python3.12-django5.0 directory. --- .../.dockerignore | 3 + .../.gitignore | 1 + .../Kraftfile | 12 ++ .../helloworld/__init__.py | 0 .../helloworld/asgi.py | 16 +++ .../helloworld/settings.py | 127 ++++++++++++++++++ .../helloworld/urls.py | 22 +++ .../helloworld/wsgi.py | 16 +++ .../main.py | 9 ++ .../manage.py | 22 +++ .../requirements.txt | 4 + 11 files changed, 232 insertions(+) create mode 100644 httpserver-python3.12-django5.0-distroless/.dockerignore create mode 100644 httpserver-python3.12-django5.0-distroless/.gitignore create mode 100644 httpserver-python3.12-django5.0-distroless/Kraftfile create mode 100644 httpserver-python3.12-django5.0-distroless/helloworld/__init__.py create mode 100644 httpserver-python3.12-django5.0-distroless/helloworld/asgi.py create mode 100644 httpserver-python3.12-django5.0-distroless/helloworld/settings.py create mode 100644 httpserver-python3.12-django5.0-distroless/helloworld/urls.py create mode 100644 httpserver-python3.12-django5.0-distroless/helloworld/wsgi.py create mode 100644 httpserver-python3.12-django5.0-distroless/main.py create mode 100755 httpserver-python3.12-django5.0-distroless/manage.py create mode 100644 httpserver-python3.12-django5.0-distroless/requirements.txt diff --git a/httpserver-python3.12-django5.0-distroless/.dockerignore b/httpserver-python3.12-django5.0-distroless/.dockerignore new file mode 100644 index 00000000..734323d0 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/.dockerignore @@ -0,0 +1,3 @@ +.unikraft/ +db.sqlite3 +venv/ diff --git a/httpserver-python3.12-django5.0-distroless/.gitignore b/httpserver-python3.12-django5.0-distroless/.gitignore new file mode 100644 index 00000000..c18dd8d8 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/httpserver-python3.12-django5.0-distroless/Kraftfile b/httpserver-python3.12-django5.0-distroless/Kraftfile new file mode 100644 index 00000000..a8a919cd --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/Kraftfile @@ -0,0 +1,12 @@ +spec: v0.7 + +runtime: base-compat:latest + +targets: + - kraftcloud/x86_64 + +rootfs: + source: ./Dockerfile + format: erofs + +cmd: ["/usr/bin/python3", "/app/main.py"] diff --git a/httpserver-python3.12-django5.0-distroless/helloworld/__init__.py b/httpserver-python3.12-django5.0-distroless/helloworld/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/httpserver-python3.12-django5.0-distroless/helloworld/asgi.py b/httpserver-python3.12-django5.0-distroless/helloworld/asgi.py new file mode 100644 index 00000000..cb171d96 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/helloworld/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for helloworld project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "helloworld.settings") + +application = get_asgi_application() diff --git a/httpserver-python3.12-django5.0-distroless/helloworld/settings.py b/httpserver-python3.12-django5.0-distroless/helloworld/settings.py new file mode 100644 index 00000000..806ee1d6 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/helloworld/settings.py @@ -0,0 +1,127 @@ +""" +Django settings for helloworld project. + +Generated by 'django-admin startproject' using Django 5.0.1. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +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 + +CSRF_TRUSTED_ORIGINS = ["https://*.unikraft.app"] + +ALLOWED_HOSTS = ["*"] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "helloworld.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "helloworld.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = "static/" + +STATIC_ROOT = "static/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/httpserver-python3.12-django5.0-distroless/helloworld/urls.py b/httpserver-python3.12-django5.0-distroless/helloworld/urls.py new file mode 100644 index 00000000..08dc5963 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/helloworld/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for helloworld project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path("admin/", admin.site.urls), +] diff --git a/httpserver-python3.12-django5.0-distroless/helloworld/wsgi.py b/httpserver-python3.12-django5.0-distroless/helloworld/wsgi.py new file mode 100644 index 00000000..512378ea --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/helloworld/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for helloworld project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "helloworld.settings") + +application = get_wsgi_application() diff --git a/httpserver-python3.12-django5.0-distroless/main.py b/httpserver-python3.12-django5.0-distroless/main.py new file mode 100644 index 00000000..e7473042 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/main.py @@ -0,0 +1,9 @@ +import bjoern +import helloworld.wsgi +from werkzeug.middleware.shared_data import SharedDataMiddleware + +app = helloworld.wsgi.application +app = SharedDataMiddleware(app, { + '/static': '/app/static', +}) +bjoern.run(app, "0.0.0.0", 80) diff --git a/httpserver-python3.12-django5.0-distroless/manage.py b/httpserver-python3.12-django5.0-distroless/manage.py new file mode 100755 index 00000000..6dee4161 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "helloworld.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/httpserver-python3.12-django5.0-distroless/requirements.txt b/httpserver-python3.12-django5.0-distroless/requirements.txt new file mode 100644 index 00000000..f8a45b93 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/requirements.txt @@ -0,0 +1,4 @@ +Django>=5.0.3 +bjoern==3.2.2 +Werkzeug>=3.0.3 +tzdata>=2026.1 From 740b989bda4999740af5b77a85880e7ef65679c9 Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Sat, 29 Aug 2026 17:36:18 +0300 Subject: [PATCH 13/28] feat: Add httpserver-python3.12-django5.0-distroless - Add python test file Slightly modified to fit the new distroless version. --- ...pserver-python3.12-django5.0-distroless.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 httpserver-python3.12-django5.0-distroless/test_httpserver-python3.12-django5.0-distroless.py diff --git a/httpserver-python3.12-django5.0-distroless/test_httpserver-python3.12-django5.0-distroless.py b/httpserver-python3.12-django5.0-distroless/test_httpserver-python3.12-django5.0-distroless.py new file mode 100644 index 00000000..26091490 --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/test_httpserver-python3.12-django5.0-distroless.py @@ -0,0 +1,34 @@ +"""End-to-end test for the ``httpserver-python3.12-django5.0-distroless`` example. + +Mirrors the manual steps from ``httpserver-python3.12-django5.0-distroless/README.md``: + +1. ``unikraft build . --output /httpserver-python312-django50-distroless:`` +2. ``unikraft run --metro -p 443:80/tls+http -m 1G --image ...`` +3. ``curl https:///admin/`` and assert the Django admin is served. + +The default Django project only defines the ``/admin/`` URL pattern, so we +test that endpoint rather than the root. +""" + +from __future__ import annotations + +from _testlib.unikraft import extract_instance_name, extract_instance_url + + +def test_django_serves_admin(build_image, run_instance, http, wait_instance): + image = build_image("httpserver-python3.12-django5.0-distroless", "httpserver-python3.12-django5.0-distroless") + + instance = run_instance( + image, + publish=["443:80/tls+http"], + memory="1G", + ) + + url = extract_instance_url(instance) + assert url, f"could not determine instance URL from: {instance!r}" + + wait_instance(extract_instance_name(instance), "running") + + resp = http(f"{url}/admin/", expected_status=200) + assert resp.status_code == 200 + assert "Django" in resp.text From fbd680957beaffe61bd1679d2dd8d838c1b47bfa Mon Sep 17 00:00:00 2001 From: Cosmin Tanasa Date: Sat, 29 Aug 2026 17:38:21 +0300 Subject: [PATCH 14/28] feat: Add httpserver-python3.12-django5.0-distroless - Add README.md Modified to fit new distroless version of python-django http server. --- .../README.md | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 httpserver-python3.12-django5.0-distroless/README.md diff --git a/httpserver-python3.12-django5.0-distroless/README.md b/httpserver-python3.12-django5.0-distroless/README.md new file mode 100644 index 00000000..8ba05ced --- /dev/null +++ b/httpserver-python3.12-django5.0-distroless/README.md @@ -0,0 +1,256 @@ +# Distroless Django HTTP Server + +This guide explains how to create and deploy a Python Django web app using a +**distroless** filesystem, built on top of +[`gcr.io/distroless/cc-debian12`](https://github.com/GoogleContainerTools/distroless) +instead of a hand-assembled `FROM scratch` image. + +To run this example, follow these steps: + +1. Install the CLI. + Use the [unikraft CLI](https://unikraft.com/docs/cli/unikraft) or the legacy [kraft CLI](https://unikraft.org/docs/cli/install). + You need a [BuildKit](https://github.com/moby/buildkit) builder. The easiest way to get one is via [Docker](https://docs.docker.com/engine/install/). + Alternatively, you can also directly set up and use BuildKit, see the [quick start](https://github.com/moby/buildkit#quick-start). + + > **Note**: + > The unikraft CLI is the current standard, while kraft is the legacy version. + > Choose one of the CLIs below and only run the commands associated with it for the rest of this guide. + +2. Clone the [`examples` repository](https://github.com/unikraft-cloud/examples) and `cd` into the `examples/httpserver-python3.12-django5.0-distroless/` directory: + + ```bash + git clone https://github.com/unikraft-cloud/examples + cd examples/httpserver-python3.12-django5.0-distroless/ + ``` + +Make sure to log into Unikraft Cloud and pick a [metro](https://unikraft.com/docs/platform/metros) close to you. +This guide uses `fra` (Frankfurt, πŸ‡©πŸ‡ͺ): + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft login +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +# Set Unikraft Cloud access token +export UKC_TOKEN=token +# Set metro to Frankfurt, DE +export UKC_METRO=fra +``` + +When done, invoke the following command to deploy this app on Unikraft Cloud: + +**Using the unikraft CLI (Recommended)** +```bash title="unikraft" +unikraft build . --output /httpserver-python312-django50-distroless:latest +unikraft run --metro fra \ + -m 1G \ + -p 443:80/tls+http \ + --scale-to-zero policy=on,cooldown-time=1000,stateful=true \ + --image /httpserver-python312-django50-distroless:latest +``` + +or + +**Using the legacy kraft CLI** +```bash title="kraft" +kraft cloud deploy \ + -M 1Gi \ + -p 443:80/tls+http \ + --scale-to-zero on \ + --scale-to-zero-stateful \ + --scale-to-zero-cooldown 1s \ + . +``` + +The output shows the instance address and other details: + +**Using the unikraft CLI (Recommended)** +```ansi title="unikraft" +metro: fra +name: httpserver-python312-django50-distroless-vt56c +uuid: d8469447-fdf6-4caf-9fea-494218ca6f72 +state: starting +image: /httpserver-python312-django50-distroless +resources: + memory: 1024MiB + vcpus: 1 +service: + uuid: 109aa11f-da45-8d57-d5e4-6eb509ee3e73 + name: dawn-sound-n5wrkxi2 + domains: + - fqdn: dawn-sound-n5wrkxi2.fra.unikraft.app +networks: +- uuid: 95f37ba4-2586-54a2-bf3e-5764c91c4fc1 + private-ip: 10.0.6.5 + mac: 12:b0:9c:af:65:e7 +timestamps: + created: just now +``` + +or + +**Using the legacy kraft CLI** +```ansi title="kraft" +[●] Deployed successfully! + β”‚ + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ name: httpserver-python312-django50-distroless-vt56c + β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ uuid: d8469447-fdf6-4caf-9fea-494218ca6f72 + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ metro: https://api.fra.unikraft.cloud/v1 + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ state: starting + β”œβ”€β”€β”€β”€β”€β”€β”€ domain: https://dawn-sound-n5wrkxi2.fra.unikraft.app + β”œβ”€β”€β”€β”€β”€β”€β”€β”€ image: oci://unikraft.io//httpserver-python312-django50-distroless@sha256:221666d414299aff54dbf10020b3d540270ee0c5907c1c6a728ca254ce8b0e50 + β”œβ”€β”€β”€β”€β”€β”€β”€ memory: 1024 MiB + β”œβ”€β”€β”€β”€β”€β”€ service: dawn-sound-n5wrkxi2 + β”œβ”€ private fqdn: httpserver-python312-django50-distroless-vt56c.internal + └─── private ip: 10.0.6.5 +``` + +In this case, the instance name is `httpserver-python312-django50-distroless-vt56c` and the address is `https://dawn-sound-n5wrkxi2.fra.unikraft.app`. +They're different for each run. + +Use `curl` to query the Unikraft Cloud instance of the Django web app server: + +```bash +curl https://dawn-sound-n5wrkxi2.fra.unikraft.app +``` + +```html + + + + + + + The install worked successfully! Congratulations! + +