Octelium is a free and open source, self-hosted zero trust access platform. It reaches everything on your home network from anywhere without forwarding a port, publishes what you want on your own domain with a managed certificate, and can deploy and host containerized applications itself.
Unified identity management for humans, workloads and AI agents
Adaptive authentication for humans and non-human identities
Identity-based, application-layer-aware access control
OpenTelemetry-native real-time visibility and auditing
Unified access architecture for private and SaaS resources
Every request from a human, a browser, a CLI, a workload or an AI agent is authenticated against your identity providers, authorized per request at the application layer by the Octelium Service in front of the resource, exported to your SIEM and observability stack whether it is allowed or denied, and only then routed to the upstream HTTP API, database, SSH server, Kubernetes cluster, LLM or MCP server.
The platform
A unified secure access platform
A homelab gets the same platform an organization does. Each capability is described in detail below, in the same order.
Each resource is represented by a Service, which is served by an identity-aware proxy that abstracts away the network details of the upstream behind it. Where that upstream lives, and whether the internet can see it, are two separate fields.
A client running on this device is asking to log in to acme.com. Approve it only if you have just started a login yourself.
Approve
Reject
Sign in with a passkey
acme.com
J
jane@acme.com
Passkey
Touch the sensor to continueVerifying…Verified
Cancel
~
~❯
New Tab
Search or type a URL
grafana
argocd
jenkins
kibana
Signing in once, then reaching Services with and without a client
A browser opens grafana.acme.com and is redirected to the Cluster sign-in page at acme.com/login.
jane signs in with a passkey verified by a biometric on the device, and the browser returns to the Grafana dashboard at grafana.acme.com with no client installed.
In a terminal, octelium connect -d opens the browser at acme.com/callback/success/approval, where the same browser session approves the login with one click.
~ ❯ octelium connect -d
Please authenticate yourself using Octelium web Portal
Waiting for you to approve the login in your browser. Press Ctrl-C to cancel.
You are now authenticated as jane
Octelium has started running in detached mode.
~ ❯ curl -s my-api/v1/users/42 | jq
{
"id": 42,
"name": "Jane Doe",
"email": "jane@acme.com"
}
~ ❯ psql -h pg-prod -d shop
psql (17.4)
Type "help" for help.
shop=> SELECT * FROM orders LIMIT 3;
id | status | total
-----+---------+--------
981 | paid | 129.00
982 | shipped | 48.50
983 | paid | 310.25
(3 rows)
shop=> \q
~ ❯ ssh bastion
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-1021-aws x86_64)
Access control
Layer-7 aware access control with policy-as-code
Access is authorized on a per-request basis, using CEL or Open Policy Agent, against the identity behind the request and the content of the request itself. Sharing one thing with someone does not share everything else.
jane, a member of the finance Group, runs curl -i billing/invoices/42 and gets HTTP/1.1 200 OK Content-Type: application/json {"id":42,"status":"paid","total":1290}. The access log records ALLOWED GET /invoices/42 with POLICY_MATCH.
jane, a member of the finance Group, runs curl -i -X DELETE billing/invoices/42 and gets HTTP/1.1 403 Forbidden X-Octelium-Unauthorized: true Content-Length: 0. The access log records DENIED DELETE /invoices/42 with NO_POLICY_MATCH.
LLM: the LLM Service openai is configured as follows.
kind: Service
metadata:
name: openai
spec:
mode: LLM
config:
authorization:
inlinePolicies:
- spec:
rules:
- effect: ALLOW
condition:
any:
of:
- match: '"ai-admins" in ctx.user.spec.groups'
- all:
of:
- match: '"ai-agents" in ctx.user.spec.groups'
- match: ctx.request.llm.operation == "GENERATE"
- match: ctx.request.llm.model == "gpt-5-mini"
support-bot, a member of the ai-agents Group, runs curl openai/v1/responses --json '{"model":"gpt-5-mini","input":"Hello"}' and gets { "id": "resp_68e8e1f0c4d0", "object": "response", "created_at": 1791628935, "status": "completed", "model": "gpt-5-mini-2025-08-07",. The access log records ALLOWED RESPONSES gpt-5-mini with POLICY_MATCH.
support-bot, a member of the ai-agents Group, runs curl openai/v1/responses --json '{"model":"gpt-5","input":"Hello"}' and gets {"error":{"message":"Octelium: unauthorized request","type":"permission_error","param":null,"code":"octelium_unauthorized"}}. The access log records DENIED RESPONSES gpt-5 with NO_POLICY_MATCH.
Kubernetes: the KUBERNETES Service k8s-prod is configured as follows.
kind: Service
metadata:
name: k8s-prod
spec:
mode: KUBERNETES
config:
authorization:
inlinePolicies:
- spec:
rules:
- effect: ALLOW
condition:
all:
of:
- match: '"sre-agents" in ctx.user.spec.groups'
- match: ctx.request.kubernetes.verb in ["get", "list"]
ops-agent, a member of the sre-agents Group, runs kubectl get configmaps -n production and gets NAME DATA AGE app-config 4 12d feature-flags 2 3d kube-root-ca.crt 1 12d. The access log records ALLOWED list configmaps · production with POLICY_MATCH.
ops-agent, a member of the sre-agents Group, runs kubectl get secrets -n production and gets Error from server (Forbidden): Octelium: Unauthorized request. The access log records DENIED list secrets · production with NO_POLICY_MATCH.
Secretless access
Keep upstream credentials out of users and workloads
Application-layer credentials are stored in the Cluster as Secrets and injected into the upstream connection once a request is authorized. Nothing has to be pasted into a bookmark, a script or a shared note.
Upstream credentials injected per request by identity in the HTTP, SSH and PostgreSQL modes
HTTP: the HTTP Service stripe proxies https://api.stripe.com and picks the upstream credential of every request with dynamic configuration rules.
elena, a member of the finance-eu Group, runs curl -s stripe/v1/account | jq '{id, country}' without holding any credential. The rule "finance-eu" in ctx.user.spec.groups selects the eu config, so Octelium injects the Secret stripe-eu and the upstream authenticates the request as acct_1Pq8EuGmbH2bXvK3. The terminal shows { "id": "acct_1Pq8EuGmbH2bXvK3", "country": "DE" }
pay-agent, a member of the finance-us Group, runs curl -s stripe/v1/account | jq '{id, country}' without holding any credential. The rule "finance-us" in ctx.user.spec.groups selects the us config, so Octelium injects the Secret stripe-us and the upstream authenticates the request as acct_1Nf2UsQ7cLm4RtW9. The terminal shows { "id": "acct_1Nf2UsQ7cLm4RtW9", "country": "US" }
SSH: the SSH Service web-01 proxies ssh://10.0.4.21 and picks the upstream credential of every request with dynamic configuration rules.
sam, a member of the sre Group, runs ssh web-01 without holding any credential. The rule "sre" in ctx.user.spec.groups selects the root config, so Octelium injects the Secret root-key and the upstream authenticates the request as root. The terminal shows Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.14.0-1015-aws x86_64) root@web-01:~# id uid=0(root) gid=0(root) groups=0(root)
noah, a member of the developers Group, runs ssh web-01 without holding any credential. The rule "developers" in ctx.user.spec.groups selects the deploy config, so Octelium injects the Secret deploy-key and the upstream authenticates the request as deploy. The terminal shows Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.14.0-1015-aws x86_64) deploy@web-01:~$ id uid=1001(deploy) gid=1001(deploy) groups=1001(deploy)
PostgreSQL: the POSTGRES Service orders-db proxies postgres://10.0.2.8:5432 and picks the upstream credential of every request with dynamic configuration rules.
priya, a member of the backend Group, runs psql -h orders-db orders without holding any credential. The rule "backend" in ctx.user.spec.groups selects the rw config, so Octelium injects the Secret pg-rw and the upstream authenticates the request as app_rw. The terminal shows psql (17.2) Type "help" for help. orders=> UPDATE orders SET status = 'shipped' WHERE id = 42; UPDATE 1
bi-agent, a member of the analytics Group, runs psql -h orders-db orders without holding any credential. The rule "analytics" in ctx.user.spec.groups selects the ro config, so Octelium injects the Secret pg-ro and the upstream authenticates the request as app_ro. The terminal shows psql (17.2) Type "help" for help. orders=> UPDATE orders SET status = 'shipped' WHERE id = 42; ERROR: permission denied for table orders
Identity
One identity model for humans, workloads and AI agents
Human and workload Users share the same identity management, authentication, access control and visibility model. The machine serving your resources, the script that backs them up and the person watching a film are all identities in the same system.
Identity-aware visibility at the application layer
Emit structured, application-layer access logs in real time and export them through OpenTelemetry OTLP to the observability, analytics and SIEM systems your organization already operates.
Logs are exported over OTLP, which fits whatever you already run at home, whether that is a Grafana stack, an OpenSearch instance or a single collector writing to disk.
Because the Cluster is yours, the record of who reached what stays inside your own boundary rather than in a provider's console.
Run the Cluster on Kubernetes infrastructure you control, scale the data plane horizontally, manage resources declaratively, automate the platform through APIs and SDKs, and optionally deploy containerized Service upstreams with the Cluster itself.
Cluster resources applied declaratively from YAML with octeliumctl and managed programmatically with the Go, TypeScript, Python and Rust SDKs
GitOps: cluster.yaml in the main branch of the ~/octelium repository describes the desired state of the Cluster. Its diff reads as follows, with + for added, ~ for changed and - for removed lines.
+ kind: Group
+ metadata:
+ name: payments
+ spec: {}
+ ---
+ kind: User
+ metadata:
+ name: alice
+ spec:
+ type: HUMAN
+ email: alice@acme.com
+ groups: ["payments"]
---
kind: User
metadata:
name: bob
spec:
type: HUMAN
~ groups: ["payments"]
---
kind: Service
metadata:
name: billing
spec:
mode: HTTP
config:
upstream:
~ url: http://10.0.3.24:8080
- ---
- kind: Service
- metadata:
- name: billing-legacy
- spec: ⋯
- ---
- kind: User
- metadata:
- name: john
- spec: ⋯
~/octelium ❯ octeliumctl apply --prune .
Group: payments Created
User: alice Created
User: bob Updated
Service: billing.default Updated
Service: billing-legacy.default Deleted
User: john Deleted
Cluster Core resources successfully applied
2 resources created
2 resources updated
2 resources deleted
Go: main.go uses the octelium-go SDK to list the Users of the Cluster and update the email and Policies of linus.
The things a homelab ends up needing, and what serves each
Most homelabs accumulate a tunnel, a reverse proxy, a certificate tool, a dynamic DNS client, an authentication portal and a pile of forwarded ports. These are the needs behind that, next to the mechanism in an Octelium Cluster that serves each one.
01
Reaching things at home from anywhere
A connected host serves the upstream over its own outbound tunnel, so nothing is port forwarded, no dynamic DNS is needed and the router configuration is untouched. That works for a NAS, a Raspberry Pi, a container or a laptop.
The very same Service can be published over HTTPS on your own domain, either behind authentication or anonymously for a personal site or a public API, with the public DNS record and the TLS certificate managed by the Cluster.
There does not have to be a machine at home at all. Managed containers deploy, scale and serve a container image as the Service upstream, which covers a static site, a Next.js or Vite application or any small service.
GitHub OAuth2 works as an identity provider out of the box, as does any OpenID Connect or SAML 2.0 provider, including a self-hosted Keycloak, Authentik or Dex that the Cluster can also serve.
Family, guests and your own automation can be separate Users in separate Groups with separate Policies, so sharing a media server does not also share the router, the NAS shell or the database.
The embedded SSH mode serves SSH from inside the octelium client itself, without a standalone SSH server on the host and without root privileges, which suits fleets of containers and small devices.
A DNS Service puts a home resolver such as Pi-hole behind identity and can authorize on the question name and record type, so ad filtering follows you off the home network.
A code server or a remote IDE endpoint is just another Service, reachable in a browser or over the tunnel, with the upstream credential held by the Cluster rather than typed into a bookmark.
The point is not that each of these is hard on its own. It is that they are usually six separate pieces of software with six configurations, and here they are fields on the same resource.
On every Octelium Cluster
The same platform capabilities ship with every deployment, whichever way you use it.
The quick installer provisions a complete single-node Cluster, including Kubernetes itself, on one fresh Linux VM with 2 vCPUs, 2 GB of RAM and a domain you own. That is enough for personal and homelab use. The Cluster itself is normally on a cheap cloud VM with a public address, while the resources it protects stay at home behind NAT.
Do I have to forward ports or run dynamic DNS?
No. A Service upstream can be an address that is only reachable from the side of a connected client. That client runs octelium connect with the --serve or --serve-all flag and serves the resource to the Cluster over its own outbound tunnel, so nothing inbound is opened on the home network and no dynamic DNS record has to track a changing address.
Can I host a public website with it?
Yes. Anonymous public access publishes an HTTP-based Service to the internet with authentication and authorization switched off, which makes the Cluster a hosting platform for a personal site or a public API. Anonymous authorization can then still be enabled so the Service filters on path, method, headers and body content while staying publicly reachable.
Is the enterprise package needed for personal use?
No. Octelium is free and open source and complete on its own. The enterprise package adds capabilities such as a web console, SCIM provisioning and secret encryption at rest, and it is free for personal, homelab and evaluation use.
Do I need to know Kubernetes?
No. An Octelium Cluster runs on top of Kubernetes, but you do not need Kubernetes experience to install, operate or use it. Day-to-day management is a set of YAML resources and a single octeliumctl apply, in the same shape as the examples on this page.
Can family members use it without installing anything?
Yes. HTTP-based resources can be published clientlessly, so a media server or a dashboard opens in an ordinary browser on a phone, a tablet or a television after a normal sign-in. The octelium client is only needed for protocols a browser cannot speak, such as SSH, databases and generic TCP and UDP.