Admiral

Server Deployment

Configure and deploy Admiral for production environments

This guide covers deploying Admiral to a production Kubernetes cluster with externally managed services. If you haven't tried Admiral yet, start with the demo mode walkthrough first.

Production vs demo

In demo mode, PostgreSQL, MinIO, and Dex all run as in-cluster subcharts. This is convenient for evaluation but not suitable for production workloads. A production deployment replaces these with externally managed services:

ConcernDemoProduction
DatabasePostgreSQL subchart (ephemeral)Managed PostgreSQL (RDS, Cloud SQL, etc.)
Object storageMinIO subchart (ephemeral)S3, S3-compatible, or GCS
AuthenticationDex with static userYour organization's OIDC provider
IngressNGINX on localhostIngress controller or Gateway API with TLS
Loggingdebug levelerror or warn
ScalingSingle replicaHPA with multiple replicas

Deployment sequence

Provision external services

Before installing the chart, set up:

  1. PostgreSQL: create a database and user with DDL privileges
  2. Object storage: create the manifests and revisions buckets
  3. OIDC provider: register an Admiral client application
  4. DNS and TLS: point a hostname at your ingress and provision a certificate

Prepare your values file

Create a values.yaml tailored to your environment. The sections below cover each component.

Install the chart

helm repo add admiral https://charts.admiral.io
helm repo update

helm upgrade --install admiral admiral/admiral \
  -f values.yaml \
  --namespace admiral \
  --create-namespace \
  --wait \
  --timeout 5m

Verify

kubectl get pods -n admiral
kubectl logs -n admiral deployment/admiral -f

Confirm the migration job completed and the server is healthy:

curl -s https://admiral.example.com/healthz
curl -s https://admiral.example.com/readyz

Component configuration

Database (PostgreSQL)

Admiral stores all platform state (environments, deployments, cluster registrations, and tokens) in PostgreSQL. The chart validates at render time that you've configured either the built-in subchart or an external connection.

External database

# Disable the built-in subchart
postgresql:
  enabled: false

# Point to your managed instance
externalDatabase:
  host: "admiral-db.abc123.us-east-1.rds.amazonaws.com"
  port: 5432
  database: "admiral"
  user: "admiral"
  password: "your-secure-password"
  sslMode: "require"

For production, store the password in a Kubernetes Secret rather than inline:

externalDatabase:
  host: "admiral-db.abc123.us-east-1.rds.amazonaws.com"
  port: 5432
  database: "admiral"
  user: "admiral"
  existingSecret: "admiral-db-credentials"
  existingSecretKey: "password"
  sslMode: "require"

Create the secret:

kubectl create secret generic admiral-db-credentials \
  --namespace admiral \
  --from-literal=password='your-secure-password'

Admiral runs schema migrations automatically as a Helm pre-install/pre-upgrade hook. The database user must have privileges to create and alter tables. See Database Migrations for details on migration modes.


Object storage

Admiral persists deployment manifests and revision bundles in object storage. Two buckets are required. Their names default to manifests and revisions but are configurable.

Avoid static credentials when possible. Admiral supports the cloud provider's default credential chain, including IRSA, EKS Pod Identity, EC2 instance profiles, and GKE Workload Identity. See Object Storage for detailed setup instructions.

Recommended for EKS. Annotate the service account with your IAM role and omit the credentials secret:

minio:
  enabled: false

serviceAccount:
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/admiral-s3-role"

objectStorage:
  type: s3
  s3:
    endpoint: "https://s3.us-east-1.amazonaws.com"
    region: "us-east-1"
    useSSL: true

buckets:
  manifests: "my-org-admiral-manifests"
  revisions: "my-org-admiral-revisions"
minio:
  enabled: false

objectStorage:
  type: s3
  s3:
    endpoint: "https://s3.us-east-1.amazonaws.com"
    region: "us-east-1"
    useSSL: true
    existingSecret: "admiral-s3-credentials"
    accessKeyKey: "access-key"
    secretKeyKey: "secret-key"

buckets:
  manifests: "my-org-admiral-manifests"
  revisions: "my-org-admiral-revisions"

Recommended for GKE. Annotate the service account and omit the credentials secret:

minio:
  enabled: false

serviceAccount:
  annotations:
    iam.gke.io/gcp-service-account: "admiral@my-project.iam.gserviceaccount.com"

objectStorage:
  type: gcs
  gcs:
    projectId: "my-gcp-project"

buckets:
  manifests: "my-org-admiral-manifests"
  revisions: "my-org-admiral-revisions"
minio:
  enabled: false

objectStorage:
  type: gcs
  gcs:
    projectId: "my-gcp-project"
    existingSecret: "admiral-gcs-credentials"
    credentialsKey: "credentials.json"

buckets:
  manifests: "my-org-admiral-manifests"
  revisions: "my-org-admiral-revisions"

Create the credentials secret from a service account key file:

kubectl create secret generic admiral-gcs-credentials \
  --namespace admiral \
  --from-file=credentials.json=./service-account.json

For MinIO, Ceph, or any S3-compatible endpoint:

minio:
  enabled: false

objectStorage:
  type: s3
  s3:
    endpoint: "https://minio.internal:9000"
    region: "us-east-1"
    useSSL: true
    existingSecret: "admiral-s3-credentials"
    accessKeyKey: "access-key"
    secretKeyKey: "secret-key"

buckets:
  manifests: "admiral-manifests"
  revisions: "admiral-revisions"

Create the credentials secret:

kubectl create secret generic admiral-s3-credentials \
  --namespace admiral \
  --from-literal=access-key='AKIAEXAMPLE' \
  --from-literal=secret-key='your-secret-key'

See Object Storage for bucket lifecycle, encryption, and credential management guidance.


Authentication (OIDC)

Admiral authenticates users via OpenID Connect. In production, connect to your organization's identity provider instead of using the built-in Dex instance.

External OIDC provider

# Disable the built-in Dex
dex:
  enabled: false

oauth2:
  issuer: "https://login.example.com"
  clientId: "admiral"
  existingSecret: "admiral-oidc-credentials"
  scopes: "openid,offline_access,email,profile"
  redirectUrl: "https://admiral.example.com/api/v1/auth/callback"
  refreshTokenTTL: "12h"

The existing secret must contain two keys:

kubectl create secret generic admiral-oidc-credentials \
  --namespace admiral \
  --from-literal=client-secret='your-oidc-client-secret' \
  --from-literal=signing-secret='a-random-32-char-string-for-jwt'

Provider-specific notes

ProviderIssuer URL formatNotes
Oktahttps://your-org.okta.comCreate a "Web Application" with authorization code flow
Azure ADhttps://login.microsoftonline.com/{tenant-id}/v2.0Register an app in Azure Portal, add redirect URI
Google Workspacehttps://accounts.google.comConfigure in Google Cloud Console → APIs & Services → Credentials
Keycloakhttps://keycloak.example.com/realms/{realm}Create a client with "Standard flow" enabled
Auth0https://your-tenant.auth0.com/Create a Regular Web Application

Using Dex in production

If you don't have an existing IdP, Dex can serve as a lightweight OIDC provider. Enable it and configure upstream connectors to LDAP, SAML, GitHub, etc.:

dex:
  enabled: true

When Dex is enabled, the chart auto-configures the issuer URL and redirect URI based on your ingress hostname. See the Dex documentation for connector configuration.

See Authentication Model for details on token types and access control.


Session management

Control how user sessions behave:

session:
  idleTimeout: "30m"    # Log out after 30 minutes of inactivity
  lifetime: "24h"       # Maximum session duration regardless of activity
  cookie:
    httpOnly: true       # Prevent JavaScript access to session cookies
    sameSite: "lax"      # CSRF protection
    secure: true         # Require HTTPS (set to true when using TLS)

Set session.cookie.secure: true whenever your deployment uses TLS. The default is false to support the HTTP-only demo mode.


Ingress

Admiral supports both traditional Kubernetes Ingress and the newer Gateway API.

ingress:
  enabled: true
  className: nginx   # or traefik, haproxy, etc.
  host: "admiral.example.com"
  tls:
    enabled: true
    secretName: "admiral-tls"
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"

When Dex is enabled, the ingress template automatically adds a rule to route /dex paths to the Dex service.

ingress:
  enabled: false

httpRoute:
  enabled: true
  hostnames:
    - "admiral.example.com"
  parentRefs:
    - name: my-gateway
      namespace: gateway-system

Requires Gateway API CRDs installed in your cluster.


Server tuning

server:
  port: 8080
  logLevel: "error"        # error, warn, info, debug
  enablePprof: false       # Go profiling endpoint (disable in prod)

  timeouts:
    default: "15s"         # Default request timeout

  stats:
    reporterType: prometheus   # Metrics backend

Scaling and availability

replicaCount: 2

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

pdb:
  create: true
  minAvailable: 1

Monitoring

If you run the Prometheus Operator, enable the ServiceMonitor to scrape Admiral metrics:

serviceMonitor:
  enabled: true
  interval: "30s"
  path: "/stats/prometheus"

Network policies

Lock down traffic to only what Admiral needs:

networkPolicy:
  enabled: true
  allowExternal: false
  allowExternalEgress: false

When allowExternalEgress is false, the chart automatically allows egress to DNS (port 53), PostgreSQL (5432), MinIO (9000), and Dex (5556).


Complete production example

Here's a full values.yaml combining the sections above:

ingress:
  enabled: true
  className: nginx
  host: "admiral.example.com"
  tls:
    enabled: true
    secretName: "admiral-tls"

server:
  logLevel: "warn"
  enablePprof: false

replicaCount: 2

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

pdb:
  create: true
  minAvailable: 1

# --- External database ---
postgresql:
  enabled: false

externalDatabase:
  host: "admiral-db.abc123.us-east-1.rds.amazonaws.com"
  port: 5432
  database: "admiral"
  user: "admiral"
  existingSecret: "admiral-db-credentials"
  existingSecretKey: "password"
  sslMode: "require"

# --- External object storage ---
minio:
  enabled: false

objectStorage:
  type: s3
  s3:
    endpoint: "https://s3.us-east-1.amazonaws.com"
    region: "us-east-1"
    useSSL: true
    existingSecret: "admiral-s3-credentials"
    accessKeyKey: "access-key"
    secretKeyKey: "secret-key"

buckets:
  manifests: "myorg-admiral-manifests"
  revisions: "myorg-admiral-revisions"

# --- External OIDC ---
dex:
  enabled: false

oauth2:
  issuer: "https://login.example.com"
  clientId: "admiral"
  existingSecret: "admiral-oidc-credentials"
  scopes: "openid,offline_access,email,profile"
  redirectUrl: "https://admiral.example.com/api/v1/auth/callback"

# --- Session ---
session:
  idleTimeout: "30m"
  lifetime: "24h"
  cookie:
    httpOnly: true
    sameSite: "lax"
    secure: true

# --- Monitoring ---
serviceMonitor:
  enabled: true
  interval: "30s"

networkPolicy:
  enabled: true
  allowExternal: false

Helm chart validation

The chart includes built-in validation that fails fast with clear error messages if your configuration is incomplete. You'll see errors like:

INSTALLATION FAILED:
  admiral: database is required.
  Either enable the built-in PostgreSQL (postgresql.enabled=true)
  or configure an external database (externalDatabase.host).

The chart validates:

  • Database: either postgresql.enabled or externalDatabase.host must be set
  • Object storage: either minio.enabled, an S3 endpoint, or a GCS project ID must be set
  • OIDC: either dex.enabled or oauth2.issuer must be set
  • External credentials: secrets must be provided when using external services

You can preview rendered templates before installing:

helm template admiral admiral/admiral -f values.yaml

What's next

On this page