Skip to main content

Requirements and defaults

  • Kubernetes, Helm 3, a dedicated namespace, and an ingress controller if exposing HTTP.
  • One server replica only; the chart rejects other values and the default namespace.
  • An existing Kubernetes Secret; the chart does not generate passwords or embed plaintext secrets in Helm release values.
  • Bundled PostgreSQL by default, one replica with a ReadWriteOnce PVC, storageClassName: rook-ceph-block, and 10Gi requested storage. No subPath mounts are used.
  • An image you have built/published or verified. The default image naming convention is ghcr.io/ricsam/browser-mcp-community:<appVersion>-community; its presence in values is not a publication claim.
The source repository remains private. The initial chart is published separately on Cloudflare R2:
The public development URL is rate-limited. The initial 0.1.1-community image supports linux/amd64. Managed deployments must override image.repository to ghcr.io/ricsam/browser-mcp and provide registry credentials.

1. Prepare namespace and secrets

The following commands are operator actions, not something chart rendering performs:
Create browser-mcp-secrets in that namespace using your secret manager. It must contain DATABASE_URL, AUTH_SECRET, SETTINGS_ENCRYPTION_KEY, and, for bundled PostgreSQL, POSTGRES_PASSWORD. Generate passwords and keys independently; do not use example strings. Managed builds also require GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET. If importing from a protected local env file, keep it outside the repository, mode 0600, and never include export prefixes:
With release name browser and the default chart name, the bundled database’s service is browser-browser-mcp-postgres. Its connection string takes this shape, with a URL-encoded password:
DATABASE_URL must match postgresql.username, postgresql.database, and the password stored as POSTGRES_PASSWORD. A different release name or fullnameOverride changes the service name. The chart deliberately does not synthesize a secret database URL. Do not change the configured password expecting an initialized PostgreSQL PVC to rotate automatically.

2. Configure values

Keep values non-secret:
On an r5d managed cluster, choose a one-label host such as browser-<project-suffix>.r5d.app from your assigned suffix, keep ingressClassName: traefik, and omit TLS entries: Cloudflare terminates public HTTPS. Use an https:// public URL. The suffix alone without a prefix is not a valid workload host. Outside that environment, configure your ingress TLS termination explicitly, for example ingress.tls: [{secretName: browser-tls, hosts: [browser.example.com]}]. Without TLS entries or an upstream TLS terminator, the chart does not magically provide HTTPS.

External PostgreSQL

Set postgresql.enabled: false. Put your external database’s URL into the same secret’s DATABASE_URL; no StatefulSet or PVC is then rendered. The database must already exist, be reachable from the migration and server pods, and permit schema migration. Configure verified TLS in the connection string according to your provider; do not disable certificate verification.

Private GHCR images on r5d

Use the worker’s configured registry auth without printing its contents. After creating the namespace, an operator can create the rotation-aware pull secret:
Then set imagePullSecrets: [{name: ghcr-pull}]. The annotation allows credential rotation; do not place auth JSON in values or source control.

3. Validate and install

Render the exact intended values and submit that rendered file to server admission before installation. The namespace must already exist for dry-run validation. The file contains secret references, not secret material.
Do not use helm template | kubectl apply as the upgrade mechanism: Helm release revisions identify migration jobs and prevent immutable Job updates. For an existing release, helm upgrade --dry-run=server additionally validates Helm’s actual release context. Fix all admission failures before proceeding.

Migration ordering

The migration Job is deliberately not a pre-install hook. Such a hook would run before bundled PostgreSQL, its Service, and ordinary release resources exist and can deadlock the first install. Instead:
  1. Helm creates the bundled database (if enabled), a revision-named migration Job, and the Deployment.
  2. The Job’s init container retries an authenticated SELECT 1 against DATABASE_URL; a listening port alone is not considered usable.
  3. The Job runs bun dist/migrate.js from the same image as the new server.
  4. A Deployment init container reads only that exact Job’s status, using namespace-scoped get RBAC. The application process does not start until the Job has a successful Complete condition. Failure leaves the server gated.
The Job is a regular Helm-managed resource without a TTL. Do not delete the current migration Job: restarted pods need its completion record. The service account credential is projected only into the startup init container, never into the app container. Neither app nor migration pods automatically mount a Kubernetes API credential. On upgrade, Recreate prevents overlap of server replicas. Migrations can overlap the shutdown of the old version, so use backward-compatible, expand/contract schema changes or stop the old server deliberately for maintenance. This is a startup gate, not an atomic database/Deployment transaction. Automatic Helm rollback does not undo database migrations.

4. Verify the release

Skip the StatefulSet check with an external database. Check the revision-named migration Job completed, Pods are Ready, the PVC is Bound to Ceph storage, and the public /healthz and /readyz return success. Inspect migration/server logs without exposing secrets. Complete one browser pairing and a harmless MCP request before declaring the deployment usable. Uninstalling the chart may retain StatefulSet PVCs. Manage backup and deletion explicitly; do not delete retained volumes as a routine troubleshooting step.