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:
| Concern | Demo | Production |
|---|---|---|
| Database | PostgreSQL subchart (ephemeral) | Managed PostgreSQL (RDS, Cloud SQL, etc.) |
| Object storage | MinIO subchart (ephemeral) | S3, S3-compatible, or GCS |
| Authentication | Dex with static user | Your organization's OIDC provider |
| Ingress | NGINX on localhost | Ingress controller or Gateway API with TLS |
| Logging | debug level | error or warn |
| Scaling | Single replica | HPA with multiple replicas |
Deployment sequence
Provision external services
Before installing the chart, set up:
- PostgreSQL: create a database and user with DDL privileges
- Object storage: create the
manifestsandrevisionsbuckets - OIDC provider: register an Admiral client application
- 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 5mVerify
kubectl get pods -n admiral
kubectl logs -n admiral deployment/admiral -fConfirm the migration job completed and the server is healthy:
curl -s https://admiral.example.com/healthz
curl -s https://admiral.example.com/readyzComponent 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.jsonFor 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
| Provider | Issuer URL format | Notes |
|---|---|---|
| Okta | https://your-org.okta.com | Create a "Web Application" with authorization code flow |
| Azure AD | https://login.microsoftonline.com/{tenant-id}/v2.0 | Register an app in Azure Portal, add redirect URI |
| Google Workspace | https://accounts.google.com | Configure in Google Cloud Console → APIs & Services → Credentials |
| Keycloak | https://keycloak.example.com/realms/{realm} | Create a client with "Standard flow" enabled |
| Auth0 | https://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: trueWhen 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-systemRequires 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 backendScaling and availability
replicaCount: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
pdb:
create: true
minAvailable: 1Monitoring
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: falseWhen 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: falseHelm 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.enabledorexternalDatabase.hostmust be set - Object storage: either
minio.enabled, an S3 endpoint, or a GCS project ID must be set - OIDC: either
dex.enabledoroauth2.issuermust 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.yamlWhat's next
- Database Migrations. Understand how schema migrations run during upgrades.
- Object Storage. Bucket policies, encryption, and lifecycle configuration.
- Authentication Model. Token types, scopes, and access control.
- Bootstrap First Cluster. Connect your first Kubernetes cluster.