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.
Pre-1.0 · self-hostable today
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.
The problem
The person with forty products and good taste is served worst by all three.
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.
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.
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
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.
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.
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.
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.
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
The storefront belongs to your customer, not to us. No runtime, no tracking, no branding smuggled into the markup.
<!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>What is in the box
Every feature here exists to help one person sell things online without renting their store from a platform.
Multi-breakpoint frames, design tokens, reusable header and footer components, live preview.
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.
Point a loop at a collection, at reviews, at a customer’s orders. One template, every row.
Paste markup and get editable nodes. Tailwind classes survive the trip.
Products, variants, collections, integer-cent prices, stock, SKUs, CSV import.
Session cart, quantity controls, a checkout page that creates a real order.
Percentage or fixed, validity windows, usage limits — scoped to the whole catalog, to chosen collections, or to individual products.
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.
One SQLite database and one uploads folder. That is the whole store — design, catalog, orders, customers.
The core ships no gateway. Install the one your market actually uses; the provider contract is four methods long.
An MCP server exposes the catalog and the page tree, so a coding agent can build and edit a store directly.
Export the published folder and host it anywhere. Nothing about your storefront needs us to exist.
Plugins
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.
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.
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.
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-sokoDeploy
Railway, Fly, a €5 VPS, or the machine under your desk. Dukafi does not care, and neither does your store.
docker run -d --name dukafi \
-p 9292:9292 \
-v dukafi-data:/data \
-e SESSION_SECRET="$(openssl rand -hex 64)" \
ghcr.io/dukafi/dukafi:latestOne container, one volume. Migrations run on start; there is no separate setup command.
Open /admin. Name the store, pick a currency, create the first admin. No terminal from here on.
Add products, design the pages, press Publish. Install the payment plugin your market uses and take a real order.
Where it actually is
Dukafi is an active implementation you can run today. Here is the line between what works and what does not.