How homa works

Drawn, not explained. Nine pictures and you know what you are buying.

1 · One building, many tenants

A Roman insula — the original apartment block. You rent one flat, not the building.

YOURS one namespace shared Kubernetes cluster · Falkenstein, EU other tenants

You get walls, a door and a meter — not the building's boiler room.

2 · Sixty seconds to your keys

Four steps. The only one that needs you is the card.

sign in (Google) choose size 1–8 vCPU card on file €0 today kubeconfig it's yours
export KUBECONFIG=~/Downloads/homa-kubeconfig.yaml
kubectl get all
kubectl apply -f https://homa.li/en/hello.yaml

A real API. Your CRDs, your operators, your manifests — no proprietary console.

3 · Inside your flat

Zooming into that yellow window.

YOUR NAMESPACE pod pod volume ingress + TLS users walls: RBAC · ResourceQuota Pod Security: restricted egress firewalled from other tenants all mutations audited neighbours can't see in Capsule tenant · up to 5 flats per account

Isolation is policy-level: RBAC, quotas, Pod Security, egress firewalled from other tenants. Not a private kernel — see picture 5.

4 · The meter only runs when you do

Two flats, same size. One is asleep, one is working.

ASLEEP nothing running ≈ €1.50 / month
WORKING 0.1 vCPU · 256 MiB ≈ €2.50 / month

Sampled every minute: €0.010/vCPU·h · €0.004/GiB·h · €0.0002/GiB storage·h · €0.002/h for the flat itself. Charged Mondays; under €0.50 rolls over.

5 · Where the line is

The honest half of the page. Read it before the card, not after.

YOU GET ✓ kubectl, your CRDs ✓ persistent volumes ✓ ingress + TLS ✓ deploy from git ✓ full audit log ✓ MCP for your agent
YOU DON'T ✗ no SLA (yet) ✗ shared kernel ✗ RWO volumes only ✗ no LoadBalancer ✗ no privileged pods ✗ no wildcard domains

✗ no privileged pods has a shape. Every namespace enforces PodSecurity restricted: a container must declare runAsNonRoot, seccompProfile: RuntimeDefault, allowPrivilegeEscalation: false and capabilities.drop: [ALL], and run as a non-root user — so a stock nginx image will not do. Miss one and Kubernetes still creates your Deployment, then refuses every Pod it tries to make. hello.yaml carries all four.

Need hardware isolation, or a contractual SLA? homa is not the right answer yet. Better you know now.

6 · The door opens outward too

Weekly billing, no commitment. Leaving takes one command.

your namespace YAML export volume tarball out

No proprietary format to escape: it is Kubernetes. Delete the flat, the meter stops.

7 · Your own front door

A real domain, a real certificate. One tool call, one DNS record, two lines of YAML.

app.you.com A → your cluster your Ingress + 1 annotation + tls: https:// http:// redirected for you — nothing to configure

1. Put the name on your list. Do this first — each tenant serves an exact list of hostnames, shown on your tenant page. Any other name — including your own domain — is rejected until it is on the list. Adding yours is self-service, from your agent: call the MCP tool connect_domain. It returns a DNS TXT record (_homa-verify.<your domain>) — set it, wait a minute, call again. The name is proven yours and allowed. Rather write a human? contact@homa.li still does it.

2. Point your domain at us. An A record to the cluster entry point, shown on your tenant page in the console — so it stays right even if the address changes. Works for a subdomain (app.you.com) and for the apex (you.com) alike — it is an A record, not a CNAME. Do this first: Let's Encrypt checks the name resolves before it issues anything.

3. Nothing to create. An Issuer named letsencrypt is already in your namespace. It is yours: your ACME account, your rate limits, nobody else's. If you ever delete it, recreate it like this:

apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef: { name: le-acme-account }
    solvers:
      - http01: { ingress: { class: traefik, serviceType: ClusterIP } }

4. Add two things to your Ingress. One annotation, one tls block. That is the whole configuration.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: site
  annotations:
    cert-manager.io/issuer: letsencrypt           # ← this line
spec:
  ingressClassName: traefik
  tls:                                            # ← and this block
    - hosts: [app.you.com]
      secretName: site-tls
  rules:
    - host: app.you.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service: { name: site, port: { number: 80 } }

Not cluster-issuer. The platform refuses a ClusterIssuer reference inside a tenant namespace, and it is not arbitrary: its ACME account has already proven our own domains, so Let's Encrypt would issue for them without a challenge. A tenant once got a real certificate for console.homa.li that way. Your own Issuer has none of that history — and none of that risk.

Exact names only — no wildcards. There is no *.example.com, and no free subdomain under a name you already have: www.you.com and you.com are two declarations, two calls. The apex is fine — it is an A record. Each name your tenant may serve is on that list and nowhere else, which is also what stops a neighbour from claiming yours.

No Certificate object to write. The annotation is enough. Let's Encrypt issues, we write the secret you named — about thirty seconds. Renewal is automatic. Nothing on your side expires.

Plain HTTP always redirects to HTTPS. Every site, including yours. Nothing to configure, nothing to forget.

Ingress is the only way in. NodePort and LoadBalancer are refused by your quota — they would open ports on the machines themselves. And *.sslip.io names share one certificate rate limit with the whole world: for anything you keep, use a domain you own.

8 · Your agent has the keys too

An MCP server, not a chatbot. Your agent runs kubectl on our machines, as your tenant.

your agent Claude, Cursor… homa /mcp OAuth, no key your namespace you approve the exact list, once

Add https://console.homa.li/mcp as a connector, sign in, and read the list before you click. It names everything the app will be able to do — including creating and deleting Kubernetes objects in your namespaces. Revoke it any time from My account.

It can see why something is broken, not just that it is. kubectl_logs returns what a container wrote — and with previous set, what the crashed instance wrote, which is the only thing that ever explains a CrashLoopBackOff. kubectl_describe returns the events attached to one object (image that will not pull, pod that will not schedule, quota reached). kubectl_rollout restarts a Deployment — the way to pick up a changed Secret without a new image — or waits for one, with a time limit so it reports instead of hanging.

And how much room is left. kubectl_get on resourcequota returns what you have used and your ceiling, on every dimension, in one call — read it before you size a workload, not after it is refused. get_usage answers a different question: it returns money over a window, not headroom.

All of it runs under your tenant's identity, in your namespace only. Before every command the server asks the Kubernetes API which account it would run as; if the answer is not yours, nothing runs. We do not read your containers — you do, through us.

Already connected? New tools do not arrive on their own. Turn the connector off and on again (Settings → Connectors), then start a new conversation. Your token stays valid — do not delete it. The list your client shows after reconnecting is the reference; the per-client setup snippets are on your My account page.

9 · From a repository to a running app

No pipeline to build, no runner to host. Your agent asks; we clone and apply.

branch or tag your repo public or private we read YAML nothing is executed running in your namespace your name HTTPS, plate 7

Point us at a repository. The MCP tool deploy_from_git clones it at a branch or a tag and applies the Kubernetes manifests it finds at the path you give — one file, or the .yaml files sitting directly in one directory. Re-deploying the same repository and path replaces cleanly instead of leaving orphaned fields behind on every commit.

Nothing from the repository is built or executed. Only object definitions are read. This is not a CI runner: you publish your image to a registry, we run what your manifests declare.

Private GitHub repositories work, and never take a token. Install the homa-deploy GitHub App on the repository — it asks for read access to contents, nothing else — then commit a .homa-deployers file at the repository root, on the default branch, holding the e-mail of your homa account, one per line. Installing the App alone is not enough: the file in the repository is what authorises, so nobody can deploy a repository by installing an App on it. GitLab: public repositories for now.

Private images. A private repository is not a private image — they are two different credentials. Upload a registry pull secret with put_file (it takes a short-lived signed URL, so the token never travels through your conversation), then reference it from your Deployment. The GitHub App does not help here; it reads manifests, not registries.

Files too: put_file puts one file into a ConfigMap or a Secret without pasting it through a chat window — or hands us a URL and we fetch it ourselves, which is the way out when your side has no route to the internet.

Run by humans, in Europe

homa is operated by Ephais SAS, a French company, on servers in Falkenstein, Germany. No third-party trackers, no external fonts, first-party analytics only. Company details · Terms of sale · Ask a question — a human answers.

Create your namespace →