Self-hosted PaaS

A self-hosted PaaS that also secures what it runs

Octelium is a free and open source, self-hosted zero trust access platform. Managed containers let the Cluster deploy, scale and serve your containerized applications as Services, so the thing that runs an application and the thing that decides who may reach it are the same declarative resource.

  • Free and open source
  • Managed TLS and DNS
service.yaml
A deployed applicationA container image becomes a Service the Cluster deploys, scales and serves itself.
A deployed application. A container image becomes a Service the Cluster deploys, scales and serves itself.
kind: Service
metadata:
  name: web
spec:
  mode: WEB
  isPublic: true
  config:
    upstream:
      container:
        port: 3000
        image: ghcr.io/acme/web:1.2.0
        replicas: 3
Public, for everyone. Anonymous access publishes it to the internet with authentication switched off.
kind: Service
metadata:
  name: blog
spec:
  mode: WEB
  isPublic: true
  isAnonymous: true
  config:
    upstream:
      container:
        port: 3000
        image: ghcr.io/acme/blog:2.4.0
Or behind your team. Without the anonymous flag, the same Service requires an identity and a Policy.
kind: Policy
metadata:
  name: staff
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.user.spec.type == "HUMAN"
            - match: '"staff" in ctx.user.spec.groups'
service-container.yaml
Deployed by the ClusterThe image is scheduled on data-plane nodes and served directly as the Service upstream.
Deployed by the Cluster. The image is scheduled on data-plane nodes and served directly as the Service upstream.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      container:
        port: 8080
        image: ghcr.io/acme/api:3.1.0
        replicas: 4
Or running somewhere else. The same Service definition covers an upstream that already runs elsewhere.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      url: http://10.0.6.40:8080
Several backends. A Service can have more than one upstream endpoint, load balanced across them.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  config:
    upstream:
      loadbalance:
        endpoints:
          - url: https://backend1.example.com
          - url: https://backend2.example.com
policy-http.yaml
The request itselfMethod, path, headers and serialized JSON body are all part of the request context.
The request itself. Method, path, headers and serialized JSON body are all part of the request context.
kind: Policy
metadata:
  name: api-read-only
spec:
  rules:
    - effect: ALLOW
      condition:
        all:
          of:
            - match: ctx.request.http.method == "GET"
            - match: ctx.request.http.path.startsWith("/v1/")
            - match: '"partners" in ctx.user.spec.groups'
Public, but not unconditional. Anonymous authorization keeps a Service public while still filtering on the request.
kind: Service
metadata:
  name: blog
spec:
  mode: HTTP
  isPublic: true
  isAnonymous: true
  config:
    upstream:
      container:
        port: 3000
        image: ghcr.io/acme/blog:2.4.0
  authorization:
    enableAnonymous: true
    inlinePolicies:
      - spec:
          rules:
            - effect: ALLOW
              condition:
                match: ctx.request.http.method == "GET"
Rate limiting. A sliding-window rate limit is a plugin on the Service, keyed per Session by default.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  isPublic: true
  config:
    upstream:
      container:
        port: 8080
        image: ghcr.io/acme/api:3.1.0
    http:
      plugins:
        - name: minute-limit
          condition:
            matchAny: true
          rateLimit:
            limit: 120
            window:
              minutes: 1
env.yaml
Configuration from SecretsEnvironment variables can be filled from Cluster Secrets rather than a checked-in file.
Configuration from Secrets. Environment variables can be filled from Cluster Secrets rather than a checked-in file.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  config:
    upstream:
      container:
        port: 8080
        image: ghcr.io/acme/api:3.1.0
        env:
          - name: LOG_LEVEL
            value: info
          - name: DATABASE_PASSWORD
            fromSecret: pg-password
A private registry. Images pull from private registries using credentials held as Cluster Secrets.
kind: Service
metadata:
  name: api
spec:
  mode: HTTP
  config:
    upstream:
      container:
        port: 8080
        image: ghcr.io/acme/api:3.1.0
        credentials:
          usernamePassword:
            username: ghcr-user
            password:
              fromSecret: ghcr-token
Upstream API keys. A deployed application can reach protected APIs without holding their credentials.
kind: Service
metadata:
  name: stripe
spec:
  mode: HTTP
  config:
    upstream:
      url: https://api.stripe.com
    http:
      auth:
        bearer:
          fromSecret: stripe-api-key
idp.yaml
Secretless workload identityCI jobs authenticate with OIDC assertions issued by the platform running them.
Secretless workload identity. CI jobs authenticate with OIDC assertions issued by the platform running them.
kind: IdentityProvider
metadata:
  name: github-actions
spec:
  oidcIdentityToken:
    issuerURL: https://token.actions.githubusercontent.com
    audience: https://example.com
A workload User. The pipeline that deploys a Service is a User with its own groups and audit trail.
kind: User
metadata:
  name: deployer
spec:
  type: WORKLOAD
  groups:
    - ci
An MCP server, deployed. Containerized MCP servers are deployed and served exactly like any other application.
kind: Service
metadata:
  name: tools-mcp
spec:
  mode: MCP
  isPublic: true
  config:
    upstream:
      container:
        port: 8080
        image: ghcr.io/acme/mcp:1.4.0
        replicas: 2
    mcp:
      endpoint: /mcp
Visibility and auditing

Real-time visibility at the application layer

OpenTelemetry-ready, application-layer aware, structured auditing and visibility emitted to your OpenTelemetry OTLP receivers, where they can be exported to your log management and SIEM tools.

Read about visibility and auditing
AccessLogOTLP
ALLOWlinus@acme.compg-prod.dbPOSTGRESquery: select id, email from users limit 50
ALLOWci-runnerprod-k8sKUBERNETESverb=list resource=pods namespace=production
DENYagent-07tools-mcpMCPtools/call name=transfer policy=mcp-tools
ALLOWjane@acme.combastionSSHsession recorded, upstream user=deploy
ALLOWagent-07openaiLLMCHAT_COMPLETIONS model=gpt-5-mini tokens=1284
octeliumctl
octeliumctl apply ./cluster
$ octeliumctl apply ./cluster
Secret: ghcr-token Updated
Secret: pg-password Updated
IdentityProvider: github Updated
Policy: staff Created
Group: staff Created
Group: ci Updated
Service: web.apps Created
Service: api.apps Created
Service: blog.apps Created
Namespace: apps Created
Cluster Core resources successfully applied
 7 resources created
 3 resources updated

 ClusterConfig updated
Responsibilities

What a deployment platform has to do, and what does each part

Running an application is only the first of the jobs a platform ends up owning. These are the rest, next to the mechanism in an Octelium Cluster that handles each one.

  1. 01

    Run the container

    A container image is set as the Service upstream and scheduled on the Cluster's data-plane nodes, using the Kubernetes infrastructure the Cluster already runs on. Custom commands and arguments are supported where the image needs them.

    Read more
  2. 02

    Pull it from where it lives

    Public and private registries are both supported, with registry credentials referenced from Cluster Secrets rather than stored as a Kubernetes pull secret you maintain separately.

    Read more
  3. 03

    Configure it

    Environment variables can be literal values, references to Cluster Secrets, or references to an existing Kubernetes secret, so configuration and credentials arrive the same way in every environment.

    Read more
  4. 04

    Scale it and bound it

    Replica counts scale an application up and down, and CPU, memory and extended resource limits are set per Service, including requesting a GPU where the Kubernetes cluster can schedule one.

    Read more
  5. 05

    Give it storage

    Kubernetes volumes are mounted into managed containers, including persistent volume claims and emptyDir volumes with a size limit, so an application that needs state is not excluded.

    Read more
  6. 06

    Put it on a domain

    A published Service gets its own public FQDN under your Cluster's domain, with the public DNS record and the TLS certificate managed by the Cluster rather than by a separate certificate workflow.

    Read more
  7. 07

    Decide who reaches it

    The same Service is either anonymous and public, published behind your identity provider, private to connected clients, or several of those at once, and that is a field rather than a different deployment.

    Read more
  8. 08

    Show what it is doing

    Every request produces an identity-aware, application-layer AccessLog streamed over OpenTelemetry, and health probes and resource limits are part of the same Service definition.

    Read more

Building and pushing images stays in whatever CI you already use. What this replaces is the layer after that: the ingress, the certificate, the authentication in front, and the audit trail behind.

Questions

Frequently asked

What can it deploy?
Any container image that listens on 0.0.0.0 on a known port. That covers web applications built with Next.js, Vite, Astro and similar frameworks, REST and gRPC APIs, MCP servers, inference servers and ordinary background services. Custom commands and arguments, environment variables from Secrets, replica counts, resource limits, security contexts, volumes and health probes are all part of the Service definition.
Does it build images as well?
No. Octelium deploys and serves an image you have already built and pushed to a registry, public or private. Building and pushing stay in whatever CI you already use, and the pipeline that does it can itself be a workload User authorized to apply the change.
How is an application exposed?
Through the same Service that runs it. Publishing it makes the Cluster's internet-facing proxy serve it at its own public FQDN with a managed TLS certificate. It can require an identity from your own identity provider, or be anonymous and open to everyone, and an anonymous Service can still filter on path, method, headers and body content.
Can it use persistent storage?
Yes. Kubernetes volumes are mounted into managed containers, including persistent volume claims and emptyDir volumes with a size limit, with mount paths and read-only flags set per Service.
Do I need Kubernetes experience?
No. An Octelium Cluster runs on top of Kubernetes and uses it to schedule managed containers, but the interface you work with is Octelium's own resources. 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.
Is Octelium free and open source?
Yes. Octelium is free and open source and is designed for single-tenant 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 provisioning and secret encryption at rest, and it is free for personal, homelab and evaluation use.
Get started

Deploy Octelium on your own infrastructure in minutes

Free and open source. Self-hosted. No vendor lock-in.