Applications Azure

Grimoire on Ubuntu 24.04 on Azure User Guide

| Product: Grimoire on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Grimoire on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Grimoire is an open source, local-first bookmark manager for people who save a lot of technical links and need to find them again later. You save a URL and Grimoire stores it, pulls out a readable copy of the page content and any media, and lets you organise everything with tags and categories. A fast keyword and full text search runs across titles, descriptions and the extracted page text, so a bookmark you saved months ago is a few keystrokes away rather than lost in a browser folder.

Under the hood Grimoire is a React single page application served by a small Bun daemon (upstream name littleimpd) that persists everything to an embedded SQLite database. There is no external database to run and no cloud service to sign in to — your library stays on your own VM.

The cloudimg image ships the free and open source, MIT licensed Grimoire release, built from the pinned upstream tag and captured into the VM so your instance starts in seconds with nothing to download. Grimoire is deliberately loopback-first and has no password login of its own, so this image does not leave it open: the daemon binds to 127.0.0.1:3210 and is never exposed directly, and nginx on port 80 is the single public listener, gated by a unique HTTP Basic credential generated on the first boot of every VM. The optional AI enrichment is switched off by default, so nothing ever leaves the VM until you choose to connect your own model provider. Backed by 24/7 cloudimg support.

Grimoire is a trademark of its respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by the Grimoire project or Robert Goniszewski. It ships the free and open source MIT licensed software.

The grimoire and nginx services active, the first-boot credential provisioning completed, and the Bun daemon footprint well within a Standard_B2s VM

What is included:

  • Grimoire 1.0.1 — the official upstream release, built from the pinned v1.0.1 tag with its React frontend compiled in
  • Bun runtime + SQLite — a single lightweight daemon on 127.0.0.1:3210, persisting to an embedded database with no external services
  • nginx — the single public listener on port 80, gated by a per instance HTTP Basic credential
  • First boot credential generation — a fresh admin web password unique to each VM, written to a file only root can read
  • AI disabled by default — the optional LLM enrichment and embeddings are off, so your library is fully private until you connect your own provider
  • A bundled self test — creates a bookmark and reads it back through the authenticated proxy, proving the platform really works end to end

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • An SSH key pair for administrative access
  • A network security group allowing inbound TCP 22 (SSH) and TCP 80 (web UI) from your own address ranges
  • Standard_B2s (2 vCPU, 4 GB RAM) or larger

Step 1: Deploy from the Azure Portal

  1. Open the Azure Marketplace and search for Grimoire on Ubuntu 24.04 LTS by cloudimg.
  2. Select Create, then choose your subscription, resource group and region.
  3. Pick a VM size of Standard_B2s or larger.
  4. Under Administrator account, select SSH public key and supply your public key.
  5. Under Inbound port rules, allow SSH (22) and HTTP (80).
  6. Select Review + create, then Create.

Step 2: Deploy from the Azure CLI

az group create --name grimoire-rg --location eastus
az vm create \
  --resource-group grimoire-rg \
  --name grimoire-vm \
  --image cloudimg:grimoire:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

az vm open-port --resource-group grimoire-rg --name grimoire-vm --port 80 --priority 1010

Step 3: Connect to your VM

ssh azureuser@<vm-ip>

Step 4: Confirm the services are running

Three units make up the deployment: grimoire (the Bun daemon), grimoire-firstboot (the one-shot that generates this VM's credential) and nginx (the public front door).

systemctl is-active grimoire nginx

Both report active. The first-boot unit is a one-shot that runs once on the very first boot to mint this VM's credential and then completes:

systemctl is-active grimoire-firstboot || echo "grimoire-firstboot: one-shot completed"

The Bun daemon is light — it comfortably fits a Standard_B2s:

ps -o rss= -C bun | awk '{s+=$1} END{printf "grimoire daemon RSS: %d MiB\n", s/1024}'

Step 5: Confirm the network exposure

Grimoire is deliberately not reachable directly. Only nginx listens publicly:

ss -tln | grep -E ':(80|3210) '

You will see 127.0.0.1:3210 for the Grimoire daemon and 0.0.0.0:80 for nginx. The daemon publishes no public port at all and is reachable only from nginx on the loopback interface.

The Grimoire daemon listening on 127.0.0.1:3210 only, with nginx listening publicly on port 80 as the single front door

Step 6: Read the per instance credential

Every VM generates its own web credential on first boot. It is written to a file only root can read:

sudo cat /root/grimoire-credentials.txt

The file records the web credential (WEB_USER and WEB_PASSWORD) and this VM's URL (GRIMOIRE_URL). Confirm the file is locked down:

ls -l /root/grimoire-credentials.txt
stat -c '%a %U:%G  %n' /root/grimoire-credentials.txt

The grimoire-credentials.txt file at mode 0600 root root, alongside the per VM environment and nginx credential files in /etc/grimoire, with the secret values themselves never displayed

Step 7: Understand the security model

This is the most important section of this guide, because Grimoire's design is unusual for a hosted app.

Grimoire is local-first and has no login of its own. Upstream it is meant to run on your own machine, bound to the loopback interface, with its browser UI and API trusted for local use only. Its own documentation is explicit that exposing the daemon straight onto a public network is not a supported mode. Left alone on a public IP, that would mean anyone who could reach the port could read and change your whole library.

So this image puts a gate in front of it, which is exactly the reverse-proxy pattern upstream recommends for remote access:

  • The Grimoire daemon binds to 127.0.0.1:3210 and is never directly reachable
  • nginx on port 80 is the only public listener, and it requires HTTP Basic authentication using this VM's unique WEB_USER and WEB_PASSWORD
  • The optional AI enrichment is disabled, so no bookmark content is ever sent to a third party

You can verify the gate yourself. Without a credential, and with a wrong one, the front door returns 401:

curl -s -o /dev/null -w 'no credential:    HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'wrong credential: HTTP %{http_code}\n' -u '<WEB_USER>:definitely-wrong' http://127.0.0.1/

With the real credential it returns 200:

curl -s -o /dev/null -w 'per VM credential: HTTP %{http_code}\n' -u '<WEB_USER>:<WEB_PASSWORD>' http://127.0.0.1/

The image also ships a self test that proves the whole platform works, not merely that a page loads. It confirms an unauthenticated request is refused, then signs in with the per VM credential, creates a bookmark whose URL is unique to this run, and reads that exact bookmark back:

sudo /usr/local/sbin/grimoire-roundtrip.sh

The bundled self test reporting that an unauthenticated request is refused with 401, the per instance credential is accepted with 200, and a bookmark is created and read back through the authenticated proxy

Step 8: Open Grimoire in your browser

Browse to your VM's URL from the credentials file:

<GRIMOIRE_URL>

Your browser asks for the web credential — enter WEB_USER and WEB_PASSWORD from Step 6. Grimoire's library then loads. Every bookmark you save is fetched, its readable content extracted and indexed, and listed with its source domain.

The Grimoire library showing saved bookmarks, each fetched and indexed, with a domains sidebar and category navigation on the left

Step 9: Save a bookmark

Select Add and paste any URL, or use the search bar as a quick-add box. Grimoire saves the link immediately and enqueues a background job that fetches the page, extracts a readable copy of the content and any media, and indexes it for search. You can also save from the API — the same call the browser extension makes:

curl -s -o /dev/null -w 'save bookmark: HTTP %{http_code}\n' \
  -u '<WEB_USER>:<WEB_PASSWORD>' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://kubernetes.io/docs/concepts/"}' \
  http://127.0.0.1/bookmarks

Step 10: Search your library

The search bar runs a keyword and full text search across titles, descriptions and the extracted page text, highlighting the matches. Because the whole page content is indexed, you can find a bookmark by a phrase you remember from the article even if it is not in the title.

A full text search for the term rust, filtered to two results with the matching text highlighted in the title and content

Select any bookmark to open its detail view, which shows the extracted readable content, any media pulled from the page, and controls to tag, categorise, archive or add notes.

A bookmark detail view showing the extracted media and readable content that Grimoire pulled from the saved page

Step 11: Optional — enable AI enrichment with your own provider

Grimoire can optionally summarise, auto-tag and semantically search your library using an LLM and an embedding model. This is disabled by default in this image, so nothing leaves your VM. To enable it, open Settings → AI and choose a provider — either a hosted provider such as OpenAI with your own API key, or a local model server such as Ollama running on a machine you control — then save. Until you do, keyword and full text search work exactly as shown above.

The Grimoire Settings page with the LLM provider set to None, showing where a customer connects their own AI provider to enable summaries, tags and semantic search

Step 12: Back up your library

Your whole library lives in a single SQLite database under /var/lib/grimoire. To take a point in time backup, stop the daemon briefly and copy the data directory:

sudo systemctl stop grimoire && sudo tar czf /tmp/grimoire-backup.tgz -C /var/lib grimoire && sudo systemctl start grimoire && echo backup-done

Grimoire also has a built-in backup feature under Settings → Backup, including scheduled snapshots and optional upload to S3-compatible storage.

Security notes

  • Change nothing to stay safe by default. The daemon is loopback-only and the single public door is gated by this VM's unique credential. There is no shared or default password in the image.
  • Restrict the network. Allow inbound TCP 80 only from address ranges you trust, and put the VM behind TLS (for example an Azure Application Gateway, or nginx with your own certificate) before exposing it to the public internet.
  • Rotate the web credential at any time by editing /etc/grimoire/htpasswd with openssl passwd -apr1 and reloading nginx.
  • Keep the OS patched. The image ships fully patched with unattended security upgrades enabled.

Support

This image is produced and supported by cloudimg. For assistance, contact cloudimg support. Grimoire itself is developed by the upstream open source project.