The modern, unified, open source secure access platform
Replace your VPN and remote access tools with a modern, unified, free and open source, identity-based zero trust platform for humans, workloads and AI agents to access hybrid infrastructure, including internal resources, microservices, AI workloads, IoT and SaaS.
- Free and open source
- Designed for self-hosting
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.
Four challenges of modern secure remote access
As humans, workloads, services, and AI agents converge on the same infrastructure, four challenges become fundamental: identity, authorization, secure connectivity, and visibility. Octelium unifies all four in a single secure access platform.
- 01
Credential sprawl
API keys, SSH keys, database passwords, kubeconfigs, and cloud credentials must be distributed, stored, rotated, and revoked across a growing fleet.
In OcteliumOctelium keeps upstream credentials as Cluster Secrets and injects them only after authorization. Users, workloads and agents never need to hold them.
- 02
Fragmented identity
Human identity lives in directories and SSO providers, while service accounts, CI jobs, workloads and AI agents accumulate separate credentials and identities elsewhere.
In OcteliumUnified identity management for humans and workloads. People authenticate through your OIDC and SAML 2.0 identity providers and native FIDO2, WebAuthn and TPM 2.0 authenticators, while workloads use OAuth2, authentication tokens or federated OIDC assertions.
- 03
Broad network access
VPNs and network-level controls often make more infrastructure reachable than a user or workload actually needs.
In OcteliumEvery resource is protected behind an identity-aware proxy. Access control is enforced at the application layer on a per-request basis with policy-as-code.
- 04
Incomplete visibility
Network and connection logs rarely explain who performed an application action, why it was authorized, and what the request actually contained.
In OcteliumOpenTelemetry-native, application-layer-aware, real-time visibility. Structured logs and metrics are exported to your SIEM providers.
A unified architecture for client-based and clientless access
One zero trust architecture built on identity-aware proxies for humans, workloads and AI agents to access internal resources behind NAT in any environments, protected SaaS resources and containerized applications.
- Client-based access over WireGuard and QUIC tunnels, which behaves as a zero-config dual-stack VPN with a stable private DNS.
- Clientless BeyondCorp access for humans via browsers and for workloads over standard OAuth2 and bearer authentication, with no client and no SDK.
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)
Identity-based access control at the application layer
Access is authorized on a per-request basis against the identity behind the request, the context around it and the content of the request itself, rather than against the network path it arrived on.
- Identity-based, context-aware ABAC with policy-as-code via CEL and OPA on a per-request basis.
- Application-layer-aware access control covers HTTP methods, paths, headers and serialized JSON bodies, Kubernetes verbs, resources and namespaces,SSH and database users, and MCP and LLM operations.
- Zero standing privileges. No admins or superuser Users. All access must be granted by a Policy.
Requests authorized one by one against CEL rules in the HTTP, LLM and Kubernetes modes
- HTTP: the HTTP Service billing is configured as follows.
- kind: Service
- metadata:
- name: billing
- spec:
- mode: HTTP
- config:
- authorization:
- inlinePolicies:
- - spec:
- rules:
- - effect: ALLOW
- condition:
- all:
- of:
- - match: '"finance" in ctx.user.spec.groups'
- - match: ctx.request.http.method == "GET"
- - match: ctx.request.http.path.startsWith("/invoices")
- 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"]
- - match: ctx.request.kubernetes.namespace == "production"
- - not: ctx.request.kubernetes.resource == "secrets"
- 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.
Access resources without distributing their credentials
Dynamic secretless access where teams and AI agents access protected infrastructure without ever holding a credential.
- Covers HTTP API keys and access tokens, SSH and RDP credentials, database passwords, kubeconfigs and mTLS certificates.
- Select different upstream identities and credentials dynamically based on the request's identity and context.
- Revoke access through Policy instead of rotating credentials across every user, workload and agent.
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
One identity model for humans, workloads and AI agents
Humans, workloads and AI agents use the same User, Group, Policy and audit model while authenticating through mechanisms appropriate to each type of identity.
- Authenticate humans via OpenID Connect or SAML 2.0 identity providers (IdPs) and enforce additional authentication through Policy.
- Support hardware-backed authentication with native WebAuthn/passkeys, TPM and TOTP.
- Federate workloads and CI/CD systems through OAuth2 or OIDC assertions instead of distributing long-lived credentials.
Signing in with a passkey and opening the Portal
- The sign-in page of the Cluster at acme.com/login offers login with Okta, with GitHub, or with a passkey.
- jane@acme.com chooses Login with a Passkey and verifies it with a biometric on the device through native WebAuthn.
- The browser is redirected to the Portal at portal.acme.com/services, which lists the Services the User can access:
- grafana.monitoring: Grafana dashboards, Web App, Namespace monitoring.
- aws-postgres.production: Amazon RDS for PostgreSQL, PostgreSQL, Namespace production.
- k8s-prod: Production EKS cluster, Kubernetes, Namespace default.
- bastion.production: SSH bastion host, SSH, Namespace production.
- github-mcp: GitHub MCP server, MCP, Namespace default.
- openai: OpenAI API, AI / LLM, Namespace default.
Secure AI agents, MCP servers and LLM access
Identity-based secretless access to MCP servers, AI/LLM providers and infrastructure for AI agents at scale.
- MCP gateway mode controls access by tool calls and their arguments, protocol versions can be pinned, and MCP servers can run as managed containers deployed by the Cluster.
- LLM/AI gateway mode controls access by model, declared tools and token limits of each inference request and audits token usage.
- Clear identity and L7-aware visibility for AI agents access to all of your internal and SaaS APIs, databases and SSH servers, with no upstream credentials distributed to the agent.
Capabilities of the LLM and MCP gateway modes, each shown as the Service configuration that enables it
- LLM gateway, Prompt instructions: Insert, replace or reject system instructions and messages. openai.yaml reads as follows.
- kind: Service
- metadata:
- name: openai
- spec:
- mode: LLM
- config:
- upstream:
- url: https://api.openai.com
- llm:
- protocol: OPENAI
- auth:
- bearer:
- fromSecret: openai-api-key
- plugins:
- - name: house-rules
- condition:
- match: ctx.request.llm.operation == "GENERATE"
- prompt:
- system:
- mode: PREPEND
- content:
- value: Follow Acme's security and data-handling rules.
- - name: caller-identity
- condition:
- match: ctx.request.llm.operation == "GENERATE"
- prompt:
- message:
- role: USER
- position: NEW_BEFORE
- content:
- eval: '"The requesting User is " + ctx.user.metadata.name'
- mode: PREPEND: first instruction
- eval: '"The requesting User is " + ctx.user.metadata.name': …User is support-bot
- LLM gateway, Guardrails: Deny secrets and redact personal data before the model sees it. openai.yaml reads as follows.
- kind: Service
- metadata:
- name: openai
- spec:
- mode: LLM
- config:
- upstream:
- url: https://api.openai.com
- llm:
- protocol: OPENAI
- auth:
- bearer:
- fromSecret: openai-api-key
- plugins:
- - name: input-dlp
- condition:
- match: ctx.request.llm.operation == "GENERATE"
- guardrail:
- leg: REQUEST
- scopes:
- - CONTENT
- - TOOL_RESULTS
- patterns:
- - secrets: {}
- action: DENY
- - type: EMAIL
- action: REDACT
- denyMessage: The request contains restricted content
- action: DENY: sk-proj-… → 403
- action: REDACT: jane@acme.com → [REDACTED:EMAIL]
- LLM gateway, Model and reasoning: Decide the model and how much it may reason, per identity. openai.yaml reads as follows.
- kind: Service
- metadata:
- name: openai
- spec:
- mode: LLM
- config:
- upstream:
- url: https://api.openai.com
- llm:
- protocol: OPENAI
- model:
- eval: '"ai-admins" in ctx.user.spec.groups ? "gpt-5" : "gpt-5-mini"'
- reasoning:
- level: MEDIUM
- auth:
- bearer:
- fromSecret: openai-api-key
- eval: '"ai-admins" in ctx.user.spec.groups ? "gpt-5" : "gpt-5-mini"': support-bot → gpt-5-mini
- level: MEDIUM: reasoning_effort: medium
- LLM gateway, Semantic routing and caching: Route by meaning and answer repeats from a per-User cache. openai.yaml reads as follows.
- kind: Service
- metadata:
- name: openai
- spec:
- mode: LLM
- config:
- upstream:
- url: https://api.openai.com
- llm:
- protocol: OPENAI
- auth:
- bearer:
- fromSecret: openai-api-key
- embedding:
- source:
- currentUpstream: true
- model: text-embedding-3-small
- plugins:
- - name: auto-model
- condition:
- match: ctx.request.llm.model == "auto"
- semanticRouter:
- fallbackModel: gpt-5-mini
- routes:
- - name: engineering
- examples:
- - Why is this Go program deadlocking?
- model: gpt-5
- - name: user-cache
- condition:
- match: ctx.request.llm.operation == "GENERATE"
- semanticCache:
- scope:
- perUser: true
- minSimilarity: 0.92
- useXCacheHeader: true
- - name: engineering: similarity 0.81
- model: gpt-5: auto → gpt-5
- minSimilarity: 0.92: 0.97
- useXCacheHeader: true: X-Cache: HIT
- LLM gateway, Token rate limiting: Sliding-window token budgets per User, Session or tenant. openai.yaml reads as follows.
- kind: Service
- metadata:
- name: openai
- spec:
- mode: LLM
- config:
- upstream:
- url: https://api.openai.com
- llm:
- protocol: OPENAI
- auth:
- bearer:
- fromSecret: openai-api-key
- plugins:
- - name: user-budget
- condition:
- match: ctx.request.llm.operation == "GENERATE"
- tokenRateLimit:
- key:
- perUser: true
- limit: 100000
- window:
- hours: 1
- defaultOutputTokens: 2048
- denyMessage: The hourly token budget has been exhausted
- limit: 100000: 98,912 used
- defaultOutputTokens: 2048: 2,048 reserved
- denyMessage: The hourly token budget has been exhausted: 429 Too Many Requests
- LLM gateway, Protocol translation: Serve the OpenAI API to agents from an Anthropic upstream. claude.yaml reads as follows.
- kind: Service
- metadata:
- name: claude
- spec:
- mode: LLM
- config:
- upstream:
- url: https://api.anthropic.com
- llm:
- protocol: OPENAI
- translation:
- upstreamProtocol: ANTHROPIC
- defaultMaxOutputTokens: 4096
- auth:
- custom:
- header: x-api-key
- value:
- fromSecret: anthropic-api-key
- protocol: OPENAI: /v1/chat/completions
- upstreamProtocol: ANTHROPIC: /v1/messages
- fromSecret: anthropic-api-key: injected upstream
- MCP gateway, Tool-level access control: Authorize every method, tool and argument per identity. finance-mcp.yaml reads as follows.
- kind: Service
- metadata:
- name: finance-mcp
- spec:
- mode: MCP
- config:
- upstream:
- url: http://10.0.5.18:8080/mcp
- mcp:
- endpoint: /mcp
- auth:
- bearer:
- fromSecret: mcp-upstream-token
- authorization:
- inlinePolicies:
- - spec:
- rules:
- - effect: ALLOW
- condition:
- match: ctx.request.mcp.method == "tools/list"
- - effect: ALLOW
- condition:
- all:
- of:
- - match: ctx.request.mcp.method == "tools/call"
- - match: ctx.request.mcp.name == "transfer"
- - match: '"finance-agents" in ctx.user.spec.groups'
- - match: ctx.request.mcp.http.bodyMap.params.arguments.amount <= 1000
- match: ctx.request.mcp.method == "tools/list": tools/list
- - match: ctx.request.mcp.method == "tools/call": tools/call
- - match: ctx.request.mcp.name == "transfer": transfer
- - match: '"finance-agents" in ctx.user.spec.groups': finance-agents
- - match: ctx.request.mcp.http.bodyMap.params.arguments.amount <= 1000: 25000
- MCP gateway, Guardrails: Catch leaked secrets in arguments and injections in results. finance-mcp.yaml reads as follows.
- kind: Service
- metadata:
- name: finance-mcp
- spec:
- mode: MCP
- config:
- upstream:
- url: http://10.0.5.18:8080/mcp
- mcp:
- endpoint: /mcp
- auth:
- bearer:
- fromSecret: mcp-upstream-token
- plugins:
- - name: tool-content
- condition:
- match: ctx.request.mcp.method == "tools/call"
- guardrail:
- leg: BOTH
- scopes:
- - TOOL_ARGUMENTS
- - TOOL_RESULTS
- patterns:
- - secrets: {}
- action: DENY
- - regex: '(?i)ignore (all )?previous instructions'
- action: DENY
- denyMessage: Restricted content was detected
- - TOOL_ARGUMENTS: agent → server
- - TOOL_RESULTS: server → agent
- action: DENY: ghp_… in arguments
- action: DENY: injection in a result
- MCP gateway, Lua scripting: Rewrite JSON-RPC messages with the caller's identity. finance-mcp.yaml reads as follows.
- kind: Service
- metadata:
- name: finance-mcp
- spec:
- mode: MCP
- config:
- upstream:
- url: http://10.0.5.18:8080/mcp
- mcp:
- endpoint: /mcp
- auth:
- bearer:
- fromSecret: mcp-upstream-token
- plugins:
- - name: add-user
- condition:
- all:
- of:
- - match: ctx.request.mcp.method == "tools/call"
- - match: ctx.request.mcp.name == "lookup"
- lua:
- inline: |
- function onRequest(ctx)
- local body = json.decode(octelium.req.getRequestBody())
- body.params.arguments.userUID = ctx.user.metadata.uid
- octelium.req.setRequestBody(json.encode(body))
- end
- body.params.arguments.userUID = ctx.user.metadata.uid: 5f0e8b2a-…
- octelium.req.setRequestBody(json.encode(body)): re-validated
- MCP gateway, Rate limiting: Bound tool calls per User and answer with JSON-RPC errors. finance-mcp.yaml reads as follows.
- kind: Service
- metadata:
- name: finance-mcp
- spec:
- mode: MCP
- config:
- upstream:
- url: http://10.0.5.18:8080/mcp
- mcp:
- endpoint: /mcp
- auth:
- bearer:
- fromSecret: mcp-upstream-token
- plugins:
- - name: user-rate
- condition:
- matchAny: true
- rateLimit:
- key:
- perUser: true
- limit: 100
- window:
- minutes: 2
- statusCode: 429
- body:
- inline: '{"jsonrpc":"2.0","error":{"code":-32000,"message":"Rate limit exceeded"},"id":null}'
- limit: 100: 100 used
- statusCode: 429: 101st call
- MCP gateway, Managed containers: Deploy and scale the MCP server itself inside the Cluster. finance-mcp.yaml reads as follows.
- kind: Service
- metadata:
- name: finance-mcp
- spec:
- mode: MCP
- config:
- upstream:
- container:
- image: ghcr.io/acme/finance-mcp:1.4.0
- port: 8080
- replicas: 3
- resourceLimit:
- cpu:
- millicores: 1000
- memory:
- megabytes: 2000
- mcp:
- endpoint: /mcp
- replicas: 3: 3/3 ready
Real-time, L7-aware visibility and auditing
OpenTelemetry-native, real-time, structured, L7-aware visibility exported to the log management and SIEM tools your organization already operates.
- Layer-7 visibility into every request: HTTP paths, methods and bodies, database queries, Kubernetes operations, SSH session recordings, DNS questions, and MCP and LLM calls with token usage.
- Provide detailed access evidence for SOC 2, ISO/IEC 27001 and internal security controls through identity-aware, application-level auditing.
A request to an internal application
The method, the path and the response code, not only a connection
- ALLOWED HTTP on hr-app.internal: GET /reviews/2026, 200.
- Method: GET
- Path: /reviews/2026
- Response: 200, text/html
- User: jane@acme.com, HUMAN
- Session: jane-rqgw1a, CLIENT
- Device: jane-mbp-14, MAC
- Decision: ALLOWED, POLICY_MATCH, p-hr-app, rule 2.
A session on a bastion host
The upstream login user, with the session recording on the entry
- ALLOWED SSH on bastion.prod: deploy@10.0.4.21:22, RECORDED.
- Log type: SESSION_RECORDING
- Upstream user: deploy
- Recording: STDOUT stream
- User: jane@acme.com, HUMAN
- Session: jane-rqgw1a, CLIENT
- Device: jane-mbp-14, MAC
- Decision: ALLOWED, POLICY_MATCH, p-bastion, rule 1.
A query against a reporting database
The statement itself, and the database user the proxy connected as
- ALLOWED POSTGRES on reporting.db: select id, total from invoices limit 50, QUERY.
- Command type: QUERY
- Database: reporting
- Database user: readonly
- User: linus@acme.com, HUMAN
- Session: linus-4kd2p, CLIENT
- Device: linus-mbp-16, MAC
- Decision: ALLOWED, POLICY_MATCH, p-reporting-db, rule 1.
A CI job listing pods in production
The verb, the resource and the namespace, so a review reads operations
- ALLOWED KUBERNETES on k8s-prod: list pods in production, 200.
- Verb: list
- Resource: pods, apiVersion v1
- Namespace: production
- User: ci-runner, WORKLOAD
- Session: ci-runner-8t2b, CLIENT
- Groups: ci, 2 Policies
- Decision: ALLOWED, POLICY_MATCH, p-k8s-readonly, rule 2.
An agent calling a tool it may not call
The JSON-RPC method, the tool and the argument that failed the rule
- DENIED MCP on tools-mcp: tools/call transfer, 403.
- Method: tools/call
- Name: transfer
- Arguments: amount=25000
- User: agent-07, WORKLOAD
- Session: agent-07-k91d, CLIENTLESS
- Groups: finance-agents, 1 Policy
- Decision: DENIED, POLICY_MATCH, mcp-tools, rule 1.
An inference request to a model provider
The protocol, the operation, the model and the tokens it used
- ALLOWED LLM on openai: CHAT_COMPLETIONS gpt-5-mini, 1284 in / 612 out.
- Protocol: OPENAI
- Operation: CHAT_COMPLETIONS
- Model: gpt-5-mini, stream false
- User: agent-07, WORKLOAD
- Session: agent-07-k91d, CLIENTLESS
- Groups: ai-users, 1 Policy
- Decision: ALLOWED, POLICY_MATCH, p-ai-users, rule 1.
Self-hosted, Kubernetes-native and GitOps-managed
Run Octelium on your own Kubernetes infrastructure and manage Cluster resources declaratively through YAML, GitOps, gRPC APIs and SDKs.
- Centralized, declarative, GitOps-friendly management, with the whole Cluster programmable over its gRPC APIs and SDKs.
- Effortlessly deploy, scale and secure access to containerized applications.
- Built on top of Kubernetes for seamless horizontal scalability and high availability.
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.
- client, err := octelium.New(ctx)
- if err != nil {⋯}
- conn, err := client.Conn(ctx)
- if err != nil {⋯}
- api := corev1.NewMainServiceClient(conn)
- users, err := api.ListUser(ctx, &corev1.ListUserOptions{})
- if err != nil {⋯}
- for _, u := range users.Items {
- fmt.Println(u.Metadata.Name)
- }
- user, err := api.GetUser(ctx, &metav1.GetOptions{Name: "linus"})
- if err != nil {⋯}
- user.Spec.Email = "linus@acme.com"
- user.Spec.Authorization = &corev1.User_Spec_Authorization{
- Policies: []string{"db-readonly"},
- }
- if _, err := api.UpdateUser(ctx, user); err != nil {⋯}
- fmt.Println("updated", user.Metadata.Name)
- ~/app ❯ go run .
- alice
- bob
- linus
- updated linus
- TypeScript: main.ts uses the @octelium/sdk SDK to list the Users of the Cluster and update the email and Policies of linus.
- const client = await OcteliumClient.create({
- domain: "acme.com",
- auth: {⋯},
- });
- const users = await client.coreV1.listUser(ListUserOptions.create());
- for (const u of users.response.items) {
- console.log(u.metadata?.name);
- }
- const { response: user } = await client.coreV1.getUser(
- GetOptions.create({ name: "linus" }),
- );
- user.spec!.email = "linus@acme.com";
- user.spec!.authorization = User_Spec_Authorization.create({
- policies: ["db-readonly"],
- });
- await client.coreV1.updateUser(user);
- console.log("updated", user.metadata?.name);
- await client.close();
- ~/app ❯ npx tsx main.ts
- alice
- bob
- linus
- updated linus
- Python: main.py uses the octelium-sdk SDK to list the Users of the Cluster and update the email and Policies of linus.
- async with await OcteliumClient.create() as client:
- users = await client.core_v1.list_user(ListUserOptions())
- for u in users.items:
- print(u.metadata.name)
- user = await client.core_v1.get_user(GetOptions(name="linus"))
- user.spec.email = "linus@acme.com"
- user.spec.authorization.policies = ["db-readonly"]
- await client.core_v1.update_user(user)
- print("updated", user.metadata.name)
- ~/app ❯ python main.py
- alice
- bob
- linus
- updated linus
- Rust: main.rs uses the octelium SDK to list the Users of the Cluster and update the email and Policies of linus.
- let client = Client::from_env().await?;
- let mut api = client.core_v1();
- let users = api.list_user(ListUserOptions::default()).await?;
- for u in users.into_inner().items {
- println!("{}", u.metadata.unwrap_or_default().name);
- }
- let options = GetOptions {
- name: "linus".into(),
- ..Default::default()
- };
- let mut user = api.get_user(options).await?.into_inner();
- let spec = user.spec.get_or_insert_default();
- spec.email = "linus@acme.com".into();
- spec.authorization.get_or_insert_default().policies = vec!["db-readonly".into()];
- let user = api.update_user(user).await?.into_inner();
- println!("updated {}", user.metadata.unwrap_or_default().name);
- ~/app ❯ cargo run -q
- alice
- bob
- linus
- updated linus
Use Octelium across your infrastructure
Start with a VPN replacement or ZTNA deployment, then apply the same identity, Services, Policies and audit model to SSH, Kubernetes, databases, APIs, MCP servers and AI agents.
Secure access
10- Modern remote access VPNZero-config client-based access over WireGuard and QUIC, with a single stable route, private dual-stack addressing and automatic private DNS.
- ZTNA and BeyondCorp platformIdentity-based, layer-7 aware access to internal applications, for humans in a browser and for workloads over standard OAuth2.
- Zero trust SSHSSH without distributing private keys or running a PKI, with per-request authorization on the login user and recorded sessions.
- Zero trust Kubernetes accesskubectl and the API server reached without a kubeconfig, authorized per verb, resource and namespace.
- Zero trust database accessPostgreSQL and MySQL reached without passwords, authorized on the database and database user, with queries captured in the logs.
- Zero trust access to SaaS APIsProtected public APIs reached without handing out long-lived API keys, with the key injected at the proxy per request.
- API gatewayA self-hosted, scalable and secure API gateway for microservices, with dynamic identity-based routing and request manipulation.
- AI gatewayIdentity-based access control, routing and visibility for OpenAI and Anthropic APIs, including model and token limits.
- MCP gatewayIdentity, authorization and auditing for MCP servers, down to the individual tool call and its arguments.
- Infrastructure for AI agentsAgents get their own identities, their own Groups and their own audit trail instead of borrowing a person's credentials.
Deployment and hosting
04- Self-hosted secure tunnelsAn ngrok and Cloudflare Tunnel alternative for identity-based as well as anonymous public access, on your own domain.
- Self-hosted PaaSDeploy, scale and host containerized applications that the Cluster serves and protects itself.
- Kubernetes ingress alternativeRoute to any Kubernetes service through dynamic, layer-7 aware policy-as-code rather than through annotations.
- Homelab infrastructureReach everything behind NAT from anywhere, and host websites, APIs and heavy containers privately or publicly.
The same platform capabilities ship with every deployment, whichever way you use it.
Frequently asked
- Is it really free and open source?
- Yes. Octelium is free and open source and is designed for self-hosting. There is no proprietary cloud-based control plane and it is not a limited edition of a separate paid product. An enterprise package is available for organizations that need capabilities such as a web console, SCIM 2.0 provisioning and Secret encryption at rest, and it is free to use, forever, for personal and evaluation use.
- How is this different from a VPN?
- A VPN grants access to a network. Octelium represents each protected resource as a Service behind an identity-aware proxy and authorizes every request against the identity behind it, the context around it and the content of the request itself. Client-based access still uses WireGuard and QUIC tunnels, but establishing one grants access to nothing on its own.
- Which protocols does it cover?
- HTTP, gRPC and web applications, SSH, Kubernetes, PostgreSQL and MySQL, RDP, DNS, SOCKS5, generic TCP and UDP, and the MCP and LLM modes for AI workloads. Each one is covered by the same identity, Policy and auditing model, and the layer-7 aware ones expose their protocol details to both Policies and logs.
- Do I need Kubernetes experience to run Octelium?
- No. Octelium runs on Kubernetes, but Kubernetes expertise is not required for a basic deployment. The quick installer provisions a complete single-node Cluster, including Kubernetes, on a fresh Linux VM or server. Larger production deployments can run on managed or on-premises multi-node Kubernetes.
Install a Cluster on your own infrastructure in minutes
One fresh Linux VM/server, and a domain you own. The installer takes care of the Cluster and all of its dependencies, Kubernetes included.
$ curl -o install-cluster.sh https://octelium.com/install-cluster.sh$ chmod +x install-cluster.sh$ ./install-cluster.sh --domain <DOMAIN>