ADR 014: Link preview images drawn by headless Chromium in the jobs role¶
- Status: Accepted
- Date: 2026-10-25
- Deciders: Owner (plan
docs/plans/trip_page_and_sharing_plan.md, agreed 2026-10-25) - Technical Context:
apps/server(TripPreview,TripPreviewJob,PreviewsController,Dockerfile), the jobs role on the worker VM (ADR 011), sharing (ADR 010)
Context¶
Public and crew links are mostly sent in WhatsApp and other messengers. Their link preview showed a generic film still. The owner wants the trip itself in the preview: the map exactly as the trip card draws it, at 5× the card's resolution, in the shape messengers show best (1.91:1).
The card's map is drawn in the browser: Leaflet with our Protomaps vector tiles painted on canvas by protomaps-leaflet, our depth layer and the OpenSeaMap seamarks. Nothing on the server draws that map, so only a browser makes the same picture.
Decision¶
- A browser on our own server. Headless Chromium photographs a page of our app that shows only the card's map (
/previews/<signed trip id>, valid 5 minutes) at 300 × 157 CSS px and device pixel ratio 5: a 1500 × 785 JPEG under 300 kB. No outside rendering or static-map service gets the trip. - Chromium in the server image, run from the jobs role.
chromiumand fonts are installed in the Rails image;TripPreviewJob(queuebackground, one at a time) starts it through Ferrum (CDP) for each image and quits it afterwards. The web role carries the package but never starts it. - Only for shared trips. An image is made when a link is created and after the trip's lines change while a link works; it is served only through a working link and deleted when none works any more, with the trip or with the account.
Not chosen:
- A separate Chromium container (Kamal accessory) on the worker VM, as the plan first said. It needs its own boot step outside the deploy, and Chrome refuses DevTools connections whose Host header is a container name. One image with one deploy is simpler. Move Chromium out if the image size or the jobs container's memory becomes a problem.
- Drawing the image in Ruby (libvips: a line on a land polygon). It is cheaper but doesn't look like the card.
- A hosted static-map or screenshot API. It would add an outside service that receives positions.
Consequences¶
- The server image grows by roughly 300–400 MB (Chromium and Noto fonts). Builds and the first deploy after this change take longer.
- Each image costs a few seconds of CPU and about 300–500 MB of memory on the worker VM, in the
backgroundqueue below uploads. The Always Free capacity (ADR 011) has room for this at beta volume; watch the jobs container's memory. - Messengers (WhatsApp/Meta, Signal, Telegram, Apple, …) fetch and keep the image of a link that is pasted. The privacy page says so.
- The preview page loads our tiles from
tiles.sailever.comlike any visitor; no new outside service. - Raster layers (depth, seamarks) are upscaled at 5× and look softer than the vector base map.