KitchenOwl on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of KitchenOwl on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. KitchenOwl is an open source app for managing a household's food. Several people share the same shopping list and items check off in real time as anyone shops, so the list stays current whoever is at the store. You keep a collection of recipes, add their ingredients to the list in one tap, and plan meals across the week on a calendar. It also tracks grocery expenses so a household can split the cost. A clean Flutter web app fronts everything and the same account works from the mobile apps.
The cloudimg image ships the free and open source, AGPL-3.0 licensed KitchenOwl, run the officially supported way as the upstream all in one container pinned by image digest and captured into the VM, so your instance starts in seconds. Because KitchenOwl's upstream token signing secret is a well known fixed value, nothing here ships with a known secret: a unique JWT signing secret is generated for each VM on first boot, before the app is reachable, and a per instance admin account with a random password is seeded once the app is healthy. Open self sign up is disabled and onboarding closes as soon as the administrator exists. Backed by 24/7 cloudimg support.
KitchenOwl is a trademark of its respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by the KitchenOwl project. It ships the free and open source AGPL-3.0 licensed self hosted software, unmodified.

What is included:
- KitchenOwl v0.7.9 (the AGPL-3.0 licensed all in one container: Flask/uWSGI API plus the built Flutter web app), pinned by image digest
- A SQLite application database, created empty on first boot and stored on a persistent Docker volume at
/data - Docker Engine with the app published to the loopback interface only, fronted by nginx on port
80with WebSocket upgrade for realtime shopping list sync kitchenowl.service,kitchenowl-firstboot.service,kitchenowl-postboot.serviceandnginx.serviceas systemd units, enabled and active on boot- A unique JWT signing secret and a per instance admin account with a random password generated per VM on first boot, never baked into the image
- No default login, no shipped secret, open self sign up disabled and an empty database on first boot
- Ubuntu 24.04 LTS base with latest security patches applied at build time
- Azure Linux Agent for seamless cloud integration and SSH key injection
- 24/7 cloudimg support with guaranteed 24 hour response SLA
Prerequisites
- Active Azure subscription, SSH public key, VNet + subnet in target region
- Subscription to the KitchenOwl listing on Azure Marketplace
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is a sensible starting point for a household. For larger households or many concurrent users use Standard_D2s_v5 or larger. NSG inbound: allow 22/tcp from your management network and 80/tcp for the web app from the networks that use it.
Step 1: Deploy from the Azure Portal
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for KitchenOwl by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create and Create.
Step 2: Deploy from the Azure CLI
RG="kitchenowl-prod"; LOCATION="eastus"; VM_NAME="kitchenowl-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/kitchenowl-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name ko-vnet --address-prefix 10.100.0.0/16 --subnet-name ko-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name ko-nsg
az network nsg rule create -g "$RG" --nsg-name ko-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name ko-nsg --name allow-http --priority 110 \
--destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name ko-vnet --subnet ko-subnet --nsg ko-nsg --public-ip-sku Standard
Step 3: Connect to your VM
ssh azureuser@<vm-ip>
Step 4: Confirm the services are running
The app runs as one all in one container under kitchenowl.service, fronted by nginx. The container is published to 127.0.0.1:8080 only; nginx on :80 is the single public front door. Confirm the services are active and see the container:
sudo systemctl is-active docker kitchenowl nginx
sudo docker compose -f /etc/kitchenowl/compose.yaml ps
You will see the services report active and the container running. KitchenOwl is bound only to the loopback interface; nginx on :80 proxies / to it and passes through WebSocket upgrades so the shopping list syncs in real time.
Step 5: Read the per instance credentials
A unique JWT signing secret was generated for this VM on the first boot, before the app was reachable, and a per instance admin account with a random password was seeded once the app was healthy and written to a root only file. Read it:
sudo cat /root/kitchenowl-credentials.txt
The file (mode 0600 root:root) holds ADMIN_USERNAME and ADMIN_PASSWORD (the account you sign in to the web app with) and WEB_URL. None of these ship in the image; every value is unique to this VM, so no two instances share a credential and there is no default login to change.

Step 6: Verify the security model
The web app and the health endpoint are reachable without authentication, but every protected API endpoint requires a bearer token, and open self sign up is disabled. Confirm that the web app loads, that an unauthenticated request to a protected endpoint is rejected with 401, and that the per instance admin credential authenticates a real call:
ADMIN_USERNAME=$(sudo grep '^ADMIN_USERNAME=' /root/kitchenowl-credentials.txt | cut -d= -f2-)
ADMIN_PASSWORD=$(sudo grep '^ADMIN_PASSWORD=' /root/kitchenowl-credentials.txt | cut -d= -f2-)
HEALTH=/api/health/8M4F88S8ooi4sMbLBfkkV7ctWwgibW6V
curl -s -o /dev/null -w 'web UI (no auth): %{http_code}\n' http://localhost/
curl -s -o /dev/null -w 'health (no auth): %{http_code}\n' "http://localhost$HEALTH"
curl -s -o /dev/null -w 'GET /api/user (no token): %{http_code}\n' -H 'Accept: application/json' http://localhost/api/user
TOKEN=$(curl -s -X POST http://localhost/api/auth \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d "{\"username\":\"$ADMIN_USERNAME\",\"password\":\"$ADMIN_PASSWORD\",\"device\":\"guide\"}" | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s -o /dev/null -w 'GET /api/user (token): %{http_code}\n' \
-H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' http://localhost/api/user
The web app returns 200, the health endpoint returns 200, the protected user endpoint is rejected with 401 for no token, and the same call with the per instance admin token returns 200. This is the security model: the API is gated by a bearer token you obtain by signing in, and there is no open registration.

The same round trip is bundled as a single script on the image, which the build and smoke tests run to prove the credential works end to end. It signs in, creates a household, adds a shopping list item and reads it back:
sudo /usr/local/sbin/kitchenowl-roundtrip.sh

Step 7: Open the web app and sign in
Browse to http://<vm-ip>/ to reach the KitchenOwl web app. Sign in with the ADMIN_USERNAME and ADMIN_PASSWORD from Step 5.

Step 8: Use the shared shopping list
After signing in, open a household to reach its shopping list. Add items by typing in the search box and tapping to add, and check them off as you shop. Every member of the household sees the same list and it syncs in real time, so whoever is at the store always has the current list. Items can be grouped by category and sorted alphabetically or by how often you buy them.

Step 9: Keep a recipe collection
Open Recipes to store your household's recipes with their ingredients, steps, cook time and yield. From a recipe you can add all of its ingredients to the shopping list in one tap, and you can import recipes from a URL. Use the search box to find a recipe instantly.

Step 10: Plan meals for the week
Open Meal planner to plan which recipes you will cook on which days. Add a recipe to a day and KitchenOwl can add its missing ingredients to the shopping list for you, so the weekly shop follows the plan. The planner starts empty and prompts you to pick from your recipes.

Step 11: Add household members
Open self sign up is disabled on this image, so new members are added by the administrator rather than by anyone who reaches the URL. Sign in as the admin, open Settings, and create accounts for your household members, or generate an invite. To enable open registration instead (anyone can create their own account), edit /etc/kitchenowl/compose.yaml, set OPEN_REGISTRATION to "true", and restart the app:
sudo systemctl restart kitchenowl
Step 12: Call the API from your own machine
Every feature of the web app is backed by the REST API, which you can call directly. Obtain a token by signing in, then call a protected endpoint with the VM address:
TOKEN=$(curl -s -X POST http://<vm-ip>/api/auth \
-H 'Content-Type: application/json' -H 'Accept: application/json' \
-d '{"username":"<ADMIN_USERNAME>","password":"<ADMIN_PASSWORD>","device":"cli"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
curl -s -H "Authorization: Bearer $TOKEN" -H 'Accept: application/json' \
"http://<vm-ip>/api/household"
The login returns a short lived access token (15 minutes) plus a refresh token; a long lived token for automation can be minted with POST /api/auth/llt. The API is documented at http://<vm-ip>/api/openapi.
Step 13: Server components
| Component | Version / Detail |
|---|---|
| Application | KitchenOwl v0.7.9 all in one (Flask/uWSGI API + Flutter web UI), pinned by image digest |
| Front door | nginx on :80 proxying / to the app with WebSocket upgrade |
| Application database | SQLite at /data/database.db (created empty on first boot, on a persistent volume) |
| Orchestration | Docker Compose under kitchenowl.service |
| JWT signing secret | per instance, generated first boot into /etc/kitchenowl/kitchenowl.env |
| Admin account | per instance random password, seeded first boot into /root/kitchenowl-credentials.txt |
| Registration | open self sign up disabled (OPEN_REGISTRATION=false) |
| Operating system | Ubuntu 24.04 LTS (patched at build) |
| License | AGPL-3.0 (KitchenOwl) |
Step 14: Managing the service
sudo systemctl status kitchenowl --no-pager | head -12
sudo docker compose -f /etc/kitchenowl/compose.yaml logs --tail 40 kitchenowl
Restart the app with sudo systemctl restart kitchenowl, follow the logs live with sudo docker compose -f /etc/kitchenowl/compose.yaml logs -f kitchenowl, and view configuration in /etc/kitchenowl/kitchenowl.env and /etc/kitchenowl/compose.yaml. After changing the environment file, restart the service to apply it.
Step 15: Use your own domain and HTTPS (production)
The image serves the web app over plain HTTP on port 80, which is convenient behind a load balancer or private network. For production you should terminate TLS in front of the VM so credentials travel encrypted:
- Front the VM with Azure Application Gateway, a load balancer with a managed certificate, or a CDN, and forward to nginx on port
80. - Or install your own certificate: add a TLS
serverblock to/etc/nginx/sites-available/kitchenowlreferencing your certificate and key, open443/tcpon the NSG, thensudo systemctl reload nginx. - If you serve the app from a custom domain, set
FRONT_URLin/etc/kitchenowl/kitchenowl.envto that URL and restart the app so realtime sync uses the right origin. The app reads its URL at first boot from the resolved public address, so use a stable public IP or set the address before first boot.
Step 16: Security recommendations
- Restrict the NSG. Allow TCP 80 (and 22 for admin) only from the networks that use the app.
- Terminate TLS in front of the app (Step 15) so the admin password and tokens are encrypted in transit.
- Rotate the seeded admin password. Sign in and change it in Settings.
- Keep registration closed. Open self sign up is disabled by default; leave it that way and add members as the administrator unless you specifically want open registration.
- Keep the app private. KitchenOwl is bound to loopback and fronted by nginx; keep it that way and never expose the container port directly.
- Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
Step 17: Support and Licensing
KitchenOwl is distributed under the GNU Affero General Public License v3.0 (AGPL-3.0). This cloudimg image bundles the unmodified official open source release; cloudimg provides the packaging, the nginx front door, the per instance secret and admin generation, the paired deploy guide, and 24/7 support with a guaranteed 24 hour response SLA. KitchenOwl is an independent open source project and this image is not affiliated with or endorsed by it.
Deploy on Azure
Launch KitchenOwl on Ubuntu 24.04 LTS by cloudimg from the Azure Marketplace and follow this guide to a working household grocery, recipe and meal planning app in minutes.