dkafi

Pre-1.0 · self-hostable today

Own your store.
One click to live.

dkafi is a self-hosted, commerce-only visual CMS. One Ruby server holds the visual editor, the catalog, the cart, checkout, media and the publisher. What it ships to your customers is plain semantic HTML — fast enough to feel instant, clean enough to read in view-source.

1
process to run
1
file to back up
0 kB
JS on a static page
0%
transaction fee

The problem

Opening a store means choosing your dependency.

The person with forty products and good taste is served worst by all three.

Hosted platforms

Shopify and friends

You rent, forever.

Fast to start, then transaction fees, an app-store tax on every add-on, themes you do not control, and a store you can never take with you.

WooCommerce stacks

plugin-fed frameworks

You own a maintenance job.

Technically yours, practically a framework held together by a dozen plugins — each one a bill, an update, and a security advisory.

Headless commerce

assemble it yourself

You own an integration project.

Maximum ownership, maximum assembly: a CMS, a storefront framework, a cart API, a host, and a developer on retainer to keep the seams closed.

A single self-hosted server can be simpler than a hosted platformand more yours than any of them — if it refuses to be general-purpose.

So Dukafi is commerce-only, deliberately. Fixed, opinionated tables — products, variants, collections, orders, discounts — instead of a universal content model. The schema is the domain. That deletes the hardest part of what a generic CMS has to build, and lets every screen be exactly right for selling.

How it works

Static by default. Dynamic by necessity.

Most commerce platforms render every page on every request because a handful of things on it are live. Dukafi classifies that per module, once, and hardcodes the answer — so merchants never think about caching and customers never wait for it.

  1. 01

    Design

    browser editor

    A real canvas — multi-breakpoint frames, design tokens, reusable components, loops over collections. Page documents and catalog edits save as JSON to one SQLite file.

  2. 02

    Publish

    pure Ruby renderer

    Render functions take data and return strings. Every page bakes to disk, Tailwind compiles only the classes that actually appear in the output, and the whole site flips live with one atomic symlink swap.

  3. 03

    Serve

    static files + fragments

    A customer gets HTML straight off disk. Only the things that genuinely change per request — stock, cart, checkout — come back as small server-rendered HTMX fragments.

Baked to disk at publish

Home, landing and content pages
change when you edit
Every /products/<slug>
one template, bound per product
Every /collections/<slug>
page 1 baked, deeper pages live
CSS bundle
content-hashed, used classes only

Rendered live, every time

Stock badge
true at the moment it is read
Cart badge and cart page
per session, never cacheable
Checkout and orders
authenticated, per customer
Collection pagination
query-driven

Editing one product in a five-hundred-product store re-bakes that product's page and the collections it appears in — not the site. Dependencies are recorded during the bake, so the publisher knows exactly what your change touched.

The output

View-source should read like it was handwritten.

The storefront belongs to your customer, not to us. No runtime, no tracking, no branding smuggled into the markup.

published/current/products/calvin-klein-ck-one.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Calvin Klein CK One</title>
  <link rel="stylesheet" href="/assets/site-d96a3a2312c4.css">
  <script src="/js/htmx.min.js" defer></script>
</head>
<body>
  <h1>Calvin Klein CK One</h1>

  <img src="/uploads/…-calvin-klein-ck-one-1.webp"
       srcset="/uploads/…-w320.webp 320w,
               /uploads/…-w640.webp 640w,
               /uploads/…-w960.webp 960w"
       sizes="auto, 100vw"
       alt="Calvin Klein CK One — fragrances"
       width="1000" height="1000" loading="lazy" decoding="async">

  <span>KES 49.99</span>

  <!-- The one live thing on the page, fetched after paint. -->
  <span class="dukafy-stock-badge"
        hx-get="/fragments/stock?product_slug=calvin-klein-ck-one"
        hx-trigger="revealed" hx-swap="outerHTML"
        aria-live="polite">Checking availability…</span>

  <!-- A real form. It posts and works with JavaScript switched off. -->
  <form class="dukafy-buy-form" method="post" action="/fragments/cart/items"
        hx-post="/fragments/cart/items" hx-swap="outerHTML">
    <input type="hidden" name="product_slug" value="calvin-klein-ck-one">
    <input type="hidden" name="variant_sku" value="FRA-CAL-CAL-006">
    <button class="dukafy-buy-button" type="submit">Add to cart</button>
    <output class="dukafy-buy-result" aria-live="polite"></output>
  </form>
</body>
</html>
No framework runtime
No React, no hydration payload, no build manifest. The only script on the page is HTMX, and only because something on it is genuinely live.
One CSS file, content-hashed
Tailwind compiles from the classes that actually appear in the rendered output — not from your source, not from a safelist. Cache it for a year.
Responsive images, done for you
Upload once; libvips produces the WebP variants, and the publisher writes the srcset, the dimensions and the lazy attributes.
It survives us
The published folder is a working static storefront on any dumb file host. If Dukafi disappeared tomorrow, your pages would not.

What is in the box

Everything a small store needs. Nothing a large one would add.

Every feature here exists to help one person sell things online without renting their store from a platform.

Design

Visual canvas

Multi-breakpoint frames, design tokens, reusable header and footer components, live preview.

Commerce modules

Product cards, price, gallery, variant picker, buy button, stock badge, cart badge — each one a canvas component and a Ruby renderer, kept honest by golden tests.

Loops

Point a loop at a collection, at reviews, at a customer’s orders. One template, every row.

Import HTML

Paste markup and get editable nodes. Tailwind classes survive the trip.

Sell

Catalog

Products, variants, collections, integer-cent prices, stock, SKUs, CSV import.

Cart and checkout

Session cart, quantity controls, a checkout page that creates a real order.

Discounts

Percentage or fixed, validity windows, usage limits — scoped to the whole catalog, to chosen collections, or to individual products.

Orders and accounts

Customers sign in, see their own orders, and open one for detail. Pages can be marked sign-in-only, with a redirect for everyone else.

Own

One file to back up

One SQLite database and one uploads folder. That is the whole store — design, catalog, orders, customers.

Payments as plugins

The core ships no gateway. Install the one your market actually uses; the provider contract is four methods long.

Editable by an agent

An MCP server exposes the catalog and the page tree, so a coding agent can build and edit a store directly.

Plain HTML on the way out

Export the published folder and host it anywhere. Nothing about your storefront needs us to exist.

Plugins

A catalogue, not a walled garden.

The core ships no payment gateway at all — a store has the providers its owner chose to install, and no others. Everything else arrives the same way.

You host your own files

Serve a manifest at a URL you control — name, description, version, screenshots, download. Submit the URL once. Ship 1.4.0 to your own server and the catalogue follows on its own; approval is a judgement about you and your plugin, not about one archive.

Paid and private, properly

A licensed listing carries no download URL at all — only your redemption endpoint. A store posts its key to you and you hand back a short-lived link. We never see the key, never proxy the file, and never sit in the middle of your customer relationship.

Checksums are not optional

A free public download must publish its sha256. Without one, a store installing your archive is trusting the host completely and cannot tell your file from one swapped in after the fact.

Plugin Soko

Soko is Swahili for market — the same root as duka, shop. It is the registry stores browse, and it is a small open Go service you can also just run yourself.

ghcr.io/dukafi/plugin-soko

Deploy

One image. Your server. Two minutes.

Railway, Fly, a €5 VPS, or the machine under your desk. Dukafi does not care, and neither does your store.

your server
docker run -d --name dukafi \
  -p 9292:9292 \
  -v dukafi-data:/data \
  -e SESSION_SECRET="$(openssl rand -hex 64)" \
  ghcr.io/dukafi/dukafi:latest
  1. 01

    Boot

    One container, one volume. Migrations run on start; there is no separate setup command.

  2. 02

    Set up

    Open /admin. Name the store, pick a currency, create the first admin. No terminal from here on.

  3. 03

    Sell

    Add products, design the pages, press Publish. Install the payment plugin your market uses and take a real order.

Database
SQLite by default. Postgres if a managed platform hands you one — same image, one environment variable.
State
Everything the merchant owns lives on the volume: the database, uploads, and the published site. Never in the image.
Backup
Copy the database file and the uploads folder. That is the entire store.
Upgrade
Pull the new image and restart. Migrations apply on boot; installed plugins are on the volume and survive.

Where it actually is

Pre-1.0, and honest about it.

Dukafi is an active implementation you can run today. Here is the line between what works and what does not.

Working today

  • Visual editor, page publishing, atomic live swaps
  • Catalog: products, variants, collections, CSV import
  • Responsive images and the used-class-only CSS bundle
  • Cart, checkout, orders, customer sign-in
  • Discounts scoped to catalog, collections or products
  • Reviews, ratings and loops over them
  • Payment providers as installable plugins
  • The plugin registry, and MCP access for coding agents

Not yet

A polished starter theme
A fresh install gives you a working store, not yet a beautiful one out of the box.
Order emails
Confirmations are on screen. SMTP is next.
The performance layer
Baked pages are already served off disk; the render cache and the Caddy front are still to come.
Continuous backup
Copying two things works. Litestream and a rehearsed restore drill do not exist yet.