Apache Guacamole
Deploy Apache Guacamole on Kubernetes — a clientless remote desktop gateway that allows RDP, VNC, SSH, and Telnet access directly from a web browser with no client installation required.
Fresh Guacamole installations use guacadmin / guacadmin as the default administrator credentials. These are
hardcoded in the schema SQL and well-known publicly. Change the password immediately after the first login via
the Guacamole admin UI.
Key Features
- guacd sidecar — protocol daemon runs alongside the web app, communicating on localhost:4822
- Dual-image architecture —
guacamole(Tomcat web app) +guacd(protocol daemon) - PostgreSQL bundled by default — no extra setup for quick start
- Schema init Job — automatic
guacamole_dbschema creation on install - OIDC, SAML, and TOTP — SSO and MFA integrations
- Tomcat reverse proxy — correct client IP via
X-Forwarded-Forsupport - Database-aware backup —
pg_dumpormysqldumpto S3 - Real listener overrides — configure Tomcat HTTP and guacd process ports independently from the Service
- Immutable official images — Guacamole and guacd 1.6.0 are pinned by multi-architecture digest
- Secure pod defaults — non-root users, read-only filesystems, seccomp, dropped capabilities, and no API token
- Ingress isolation — NetworkPolicy enabled by default
Chart Documentation
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install guacamole helmforge/guacamole -f values.yaml
OCI registry:
helm install guacamole oci://ghcr.io/helmforgedev/helm/guacamole -f values.yaml
Deployment Examples
# values.yaml — Guacamole with bundled PostgreSQL (default)
# Default credentials: guacadmin / guacadmin — CHANGE IMMEDIATELY after first login
guacd:
logLevel: info
postgresql:
enabled: true
auth:
database: guacamole_db
username: guacamole_user
password: 'strong-db-password'
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: remote.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: guacamole-tls
hosts:
- remote.example.com# values.yaml — Guacamole with OIDC SSO (Keycloak example)
# OIDC replaces local database authentication for login
# redirectUri is auto-detected from ingress if left empty
oidc:
enabled: true
authorizationEndpoint: 'https://auth.example.com/realms/myrealm/protocol/openid-connect/auth'
jwksEndpoint: 'https://auth.example.com/realms/myrealm/protocol/openid-connect/certs'
issuer: 'https://auth.example.com/realms/myrealm'
clientId: guacamole
redirectUri: 'https://remote.example.com/' # auto-detected from ingress if empty
scope: 'openid email profile'
usernameClaim: preferred_username
groupsClaim: groups
totp:
enabled: true # combine OIDC with TOTP for 2FA on top of SSO
postgresql:
enabled: true
auth:
password: 'strong-db-password'
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: remote.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: guacamole-tls
hosts:
- remote.example.com# values.yaml — Guacamole with SAML SSO
saml:
enabled: true
idpMetadataUrl: 'https://auth.example.com/realms/myrealm/protocol/saml/descriptor'
entityId: 'https://remote.example.com/'
callbackUrl: 'https://remote.example.com/' # auto-detected from ingress if empty
strict: true
groupAttribute: groups
postgresql:
enabled: true
auth:
password: 'strong-db-password'
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: remote.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Guacamole with external PostgreSQL and S3 backup
postgresql:
enabled: false
mysql:
enabled: false
database:
type: postgresql
external:
host: postgres.database.svc.cluster.local
port: 5432
name: guacamole_db
username: guacamole_user
existingSecret: guacamole-db-credentials
existingSecretPasswordKey: password
backup:
enabled: true
schedule: '0 2 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: guacamole-backups
existingSecret: guacamole-s3-credentials
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: remote.example.com
paths:
- path: /
pathType: PrefixApplication and Service Ports
containerPorts.http configures the real Tomcat HTTP connector through the
official image’s supported entrypoint.d startup extension. guacd.port
configures both the daemon listener and the web application’s local connection.
service.port remains the independent client-facing Kubernetes Service port.
containerPorts:
http: 18080
guacd:
port: 14822
service:
port: 8080
The chart’s k3d validation requires both overridden listeners to become Ready; these settings do not change only Kubernetes metadata.
Configuration Reference
Images
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/guacamole/guacamole |
Guacamole web app image. |
image.tag |
string | "1.6.0" |
Image tag. |
image.digest |
string | pinned 1.6.0 digest | Immutable web image manifest. |
guacd.image.repository |
string | docker.io/guacamole/guacd |
guacd protocol daemon image. |
guacd.image.tag |
string | "1.6.0" |
guacd image tag. |
guacd.image.digest |
string | pinned 1.6.0 digest | Immutable daemon image manifest. |
Guacamole
| Parameter | Type | Default | Description |
|---|---|---|---|
guacamole.contextPath |
string | ROOT |
Web context path. ROOT = serve at /. Change to guacamole for /guacamole/. |
guacamole.extraEnv |
array | [] |
Extra environment variables. |
containerPorts.http |
integer | 8080 |
Actual Tomcat HTTP listener. |
guacd.port |
integer | 4822 |
Actual internal guacd daemon listener. |
guacd.logLevel |
string | info |
guacd log level: info, debug, warning, error. |
Reverse Proxy
| Parameter | Type | Default | Description |
|---|---|---|---|
proxy.enabled |
boolean | true |
Enable Tomcat RemoteIpValve for correct client IPs. |
proxy.ipHeader |
string | X-Forwarded-For |
Header containing the real client IP. |
proxy.protocolHeader |
string | X-Forwarded-Proto |
Header containing the protocol (http/https). |
OIDC
| Parameter | Type | Default | Description |
|---|---|---|---|
oidc.enabled |
boolean | false |
Enable OIDC SSO. |
oidc.authorizationEndpoint |
string | "" |
OIDC authorization endpoint URL. |
oidc.jwksEndpoint |
string | "" |
JWKS token validation endpoint URL. |
oidc.issuer |
string | "" |
Expected token issuer. |
oidc.clientId |
string | "" |
OIDC client ID. |
oidc.redirectUri |
string | "" |
Redirect URI. Auto-detected from ingress if empty. |
oidc.usernameClaim |
string | preferred_username |
JWT claim for the username. |
oidc.groupsClaim |
string | groups |
JWT claim for groups. |
SAML
| Parameter | Type | Default | Description |
|---|---|---|---|
saml.enabled |
boolean | false |
Enable SAML SSO. |
saml.idpMetadataUrl |
string | "" |
IdP SAML metadata URL. |
saml.entityId |
string | "" |
Service provider entity ID. |
saml.callbackUrl |
string | "" |
Callback URL. Auto-detected from ingress if empty. |
saml.strict |
boolean | true |
Enforce certificate validation. |
saml.groupAttribute |
string | groups |
SAML group attribute name. |
totp.enabled |
boolean | false |
Enable TOTP two-factor authentication. |
Database and Init Job
| Parameter | Type | Default | Description |
|---|---|---|---|
database.type |
string | postgresql |
Database type: postgresql or mysql. |
database.external.host |
string | "" |
External database hostname. |
database.external.port |
string | "" |
External database port. Defaults by database type. |
database.external.name |
string | guacamole_db |
External database name. |
database.external.username |
string | guacamole_user |
External database user. |
database.external.password |
string | "" |
Inline external database password. Prefer secrets. |
database.external.existingSecret |
string | "" |
Existing secret with database password. |
database.external.existingSecretPasswordKey |
string | password |
Password key in the external database secret. |
initDb.enabled |
boolean | true |
Run schema init Job on install (safe for upgrades). |
postgresql.enabled |
boolean | true |
Deploy the bundled PostgreSQL subchart. |
postgresql.auth.password |
string | "" |
Password. Required when using the subchart. |
mysql.enabled |
boolean | false |
Deploy the bundled MySQL subchart. |
mysql.auth.password |
string | "" |
Password. Required when using the MySQL subchart. |
Service and Ingress
| Parameter | Type | Default | Description |
|---|---|---|---|
service.type |
string | ClusterIP |
Service type. |
service.port |
integer | 80 |
Service port. |
ingress.enabled |
boolean | false |
Enable an Ingress resource. |
ingress.ingressClassName |
string | traefik |
Ingress class name. |
networkPolicy.enabled |
boolean | true |
Isolate inbound application traffic. |
serviceAccount.automountServiceAccountToken |
boolean | false |
Mount the Kubernetes API token. |
Backup
Database-aware: pg_dump for PostgreSQL, mysqldump for MySQL.
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled |
boolean | false |
Enable scheduled database S3 backup. |
backup.schedule |
string | "0 2 * * *" |
Cron schedule. |
backup.s3.endpoint |
string | "" |
S3-compatible endpoint URL. |
backup.s3.bucket |
string | "" |
Target bucket name. |
backup.s3.prefix |
string | guacamole |
Prefix inside the target bucket. |
backup.s3.existingSecret |
string | "" |
Existing secret with S3 credentials. |
backup.s3.accessKey |
string | "" |
Inline S3 access key. Prefer secrets. |
backup.s3.secretKey |
string | "" |
Inline S3 secret key. Prefer secrets. |
extraManifests |
array | [] |
Extra Kubernetes manifests. |
Security and Operations
The official web and guacd images run as their upstream non-root UIDs. The
default pod uses RuntimeDefault seccomp, a read-only root filesystem, no
privilege escalation, no Linux capabilities, explicit writable /tmp volumes,
and no mounted Kubernetes API token. Keep these defaults unless an audited
extension requires a documented exception.
The default NetworkPolicy isolates ingress but leaves egress open because
guacd must reach operator-selected remote desktop targets and the web
application may contact identity providers. Add ingress-controller peers with
networkPolicy.ingressFrom when they are outside the release. Use existing
Secrets or External Secrets for production database and backup credentials,
and change the default guacadmin password immediately.
kubectl -n <namespace> rollout status deployment/<release>-guacamole
kubectl -n <namespace> logs deployment/<release>-guacamole -c guacamole
kubectl -n <namespace> logs deployment/<release>-guacamole -c guacd
kubectl -n <namespace> get events --sort-by=.lastTimestamp