Kubernetes
Plain manifests for the whole stack, one namespace (ovc-infra) and
ReadWriteOnce PVCs for persistent data.
Container images
Section titled “Container images”ovc-frontend and ovc-backend are built by GitHub Actions and published to
GHCR (guacd is the upstream guacamole/guacd:1.6.0):
| Component | Image |
|---|---|
ovc-frontend |
ghcr.io/openvcenter/ovc-frontend:latest |
ovc-backend |
ghcr.io/openvcenter/ovc-backend:latest |
ovc-backend’s worker Deployment reuses the same ovc-backend image with
command: python -m app.worker instead of uvicorn. ovc-agent-hyperv isn’t
distributed as a container - it’s a Windows service binary uploaded through
the frontend’s Agent Management dialog (stored by the backend) and installed
directly on each Hyper-V host.
Backing services
Section titled “Backing services”PostgreSQL, RabbitMQ and Valkey, each with its own PVC:
PGDATAis set one level below the mount point so initdb never trips over a non-empty directory (lost+found, etc).RABBITMQ_NODENAMEpins the Erlang node name so a pod restart keeps the same Mnesia database.guacd(no PVC) is its own Deployment behind a ClusterIP Service, see ovc-backend and ovc-frontend below.
Per the project convention, container env vars - passwords included - live
in ConfigMaps. For a production deployment, move the passwords into
Secrets and switch envFrom.configMapRef to secretRef.
OIDC provider
Section titled “OIDC provider”ovc-backend and ovc-frontend default to real OIDC login against a
provider - any works (Keycloak, Auth0, Okta, Entra ID). The user’s groups are
read from a configurable claim (resource_access.<client_id>.roles by default) -
see Access control below.
The provider has two addresses, and each service needs to know both:
| Setting | ovc-frontend |
ovc-backend |
|---|---|---|
Issuer, as the browser reaches it (it ends up in the token’s iss) |
OIDC_ISSUER |
OVC_OIDC_ISSUER |
| The same provider, as the server reaches it | OIDC_INTERNAL_URL (issuer URL) |
OVC_OIDC_JWKS_URL (JWKS URL) |
In containers they differ: the browser uses e.g. http://keycloak.example.com,
while the pods use the Service name. ovc-frontend fetches only the discovery
document from OIDC_INTERNAL_URL, so the provider must answer with a
browser-facing authorization_endpoint and server-reachable token, userinfo
and JWKS endpoints. With Keycloak, set KC_HOSTNAME to its public URL and
KC_HOSTNAME_BACKCHANNEL_DYNAMIC=true. Without OIDC_INTERNAL_URL the
frontend uses OIDC_ISSUER for everything, which is right when the server can
reach the public URL too.
If the provider is unreachable when ovc-frontend starts, the login screen
says so and the frontend retries discovery every few seconds - no restart
needed once the provider is up.
No IdP, lab/demo deployment: set OVC_AUTH_MODE: stub in both
ovc-backend’s and ovc-frontend’s config instead.
Access control
Section titled “Access control”Access is granted to groups, never to individual users. The backend reads
the user’s groups from one token claim, OVC_OIDC_GROUPS_CLAIM (a dot path;
default resource_access.${client_id}.roles, i.e. Keycloak client roles). Set
the frontend’s OIDC_GROUPS_CLAIM to the same path. For another provider,
point both at its groups claim, e.g. groups or roles for Entra ID.
| Group | Access |
|---|---|
ADMINISTRATOR |
Every privilege on every object, with no setup needed. Configurable via OVC_ADMIN_GROUP on the backend. |
| (any other group) | Logs in. Sees and does only what the permissions granted to its groups allow; none means an empty tree. |
| (no group at all) | Redirected to /access-denied right after login. |
Permissions are managed in the UI: Administration ▸ Roles defines roles (named sets of privileges such as power, console or snapshots), and each cluster, host, folder and VM has a Permissions tab that grants a role to a group on that object. A permission propagates to the objects below it unless told not to; the built-in No access role blocks a branch.
For a first deployment, put at least one user in ADMINISTRATOR so someone
can log in, add hosts and grant permissions to the other groups.
ovc-backend and ovc-frontend
Section titled “ovc-backend and ovc-frontend”The API and the worker are two Deployments sharing one image and ConfigMap.
OVC_AGENT_STORAGE=local needs its own PVC (mounted only on the API pod) so
uploaded ovc-agent binaries survive a redeploy.
All of ovc-frontend’s config is runtime (changeable without a
rebuild): API_URL, GUACD_URL, OIDC_* and BETTER_AUTH_* are read
from the environment at runtime.
ovc-guacd is a plain Deployment behind the ovc-guacd-service ClusterIP
Service - the same shape as the Docker Compose setup. The frontend’s own
server serves the consoles’ Guacamole tunnel (/webrdp/tunnel) and talks to
guacd over the cluster-internal DNS (GUACD_URL: ovc-guacd-service:4822); the
browser never connects to guacd, so it needs no NodePort. The console tunnels
live in the frontend’s memory, so keep one frontend replica or use sticky
sessions.
Deploying
Section titled “Deploying”See quick-install/kubernetes
for the actual manifests, NodePorts, optional Ingress and apply.sh /
delete.sh. apply.sh asks for a base domain once (default
openvcenter.local; the hosts become frontend.<domain>, keycloak.<domain>,
rabbitmq-management.<domain> and, for the agents, rabbitmq.<domain>), applies
the manifests with it,
and finishes with a sign-in check from inside the frontend pod.
Once ovc-frontend is reachable, open it to add a host and install its
agent - see
Running in dev mode › Agent
for the Add Host / Setup Agent steps.