HH-Lion Deployment Setup Guide
Step-by-step instructions to deploy hh-lion to engineering (DigitalOcean DOKS) and production (AWS EKS).
Table of Contents
- Architecture Overview
- Prerequisites
- Kubernetes Cluster Setup
- GitHub Environments Setup
- GitHub Repository Secrets
- GitHub Environment Variables (per env)
- GitHub Environment Secrets (per env)
- AWS OIDC Setup (Production)
- Create Kubernetes Namespace
- Trigger a Deployment
- Verify Deployment
- Troubleshooting
1. Architecture Overview
┌─────────────┐ push develop ┌──────────────────┐
│ Developer │ ──────────────────► │ GitHub Actions │
│ / GitHub │ or manual │ deploy.yml │
└─────────────┘ dispatch └────────┬─────────┘
│
┌─────────────────────────┼──────────────────────────┐
│ Engineering │ Production │
▼ ▼ │
┌──────────────┐ ┌──────────────┐ │
│ DOCR Registry│ │ GHCR Registry│ │
└──────┬───────┘ └──────┬───────┘ │
│ │ │
┌──────▼───────┐ ┌──────▼───────┐ │
│ DigitalOcean │ │ AWS EKS │ │
│ DOKS │ │ (OIDC) │ │
└──────────────┘ └──────────────┘ │
│
- Engineering: Auto-deploys on push to
developbranch. Images pushed to DigitalOcean Container Registry (DOCR). - Production: Manual
workflow_dispatchonly. Images pushed to GitHub Container Registry (GHCR). Auth via OIDC.
2. Prerequisites
Before starting, ensure you have:
- A DigitalOcean account with a managed Kubernetes cluster (DOKS) for engineering
- An AWS account with an EKS cluster for production
- GitHub repository
hungryhub-team/hh-lionwith admin access - DigitalOcean CLI (
doctl) installed locally (optional, for debugging) - AWS CLI (
aws) installed locally (optional, for debugging) -
kubectlinstalled locally
3. Kubernetes Cluster Setup
Engineering (DigitalOcean DOKS)
- Create a DOKS cluster via the DigitalOcean console.
- Note the Cluster ID (visible in the cluster URL or via
doctl kubernetes cluster list). - Create a DigitalOcean Container Registry (DOCR) if you don’t have one.
- Note the Registry URL (e.g.,
registry.digitalocean.com/hungryhub).
Production (AWS EKS)
- Create an EKS cluster via AWS Console or Terraform.
- Note the cluster name and region.
- Set up OIDC federation (see Section 8).
3a. Optional: Deploy MLflow in Kubernetes (Engineering + Production)
Serving mode in engineering and production requires a real model reference (FORCE_MOCK_MODEL=false and valid MLFLOW_MODEL_NAME + MLFLOW_MODEL_ALIAS). The app needs a reachable MLflow tracking server. This repo now includes:
For copy-paste engineering bootstrap commands (from empty data to non-mock deploy), see Engineering Bootstrap Command Sheet.
For production training-data bootstrap from booking_production, see Production Training Bootstrap Runbook.
k8s/engineering/mlflow.yamlk8s/production/mlflow.yaml
Each env-specific manifest defines the Deployment/Service/Ingress only and expects:
mlflow-configConfigMapmlflow-secretsSecret
There is no shared root k8s/mlflow.yaml template anymore; k8s/engineering/mlflow.yaml
and k8s/production/mlflow.yaml are the canonical manifests for cluster deployments.
You can create those manually (see k8s/README.md) or let GitHub Actions create/update them from environment vars and secrets.
Apply per environment after editing placeholders:
# Engineering cluster
kubectl apply -f k8s/engineering/mlflow.yaml
# Production cluster
kubectl apply -f k8s/production/mlflow.yaml
Then set hh-lion environment values:
MLFLOW_TRACKING_URI-> your MLflow URL (Ingress host or internal service URL)MLFLOW_MODEL_NAME+MLFLOW_MODEL_ALIAS-> preferred registered-model reference for serving (for examplehomepage_two_tower+production)
The hh-lion serving app no longer needs direct object-store credentials to download model artifacts when the MLflow server runs in artifact-proxy mode. The MLflow server still needs its own R2/S3 credentials.
Aliases used for serving should resolve to model versions logged through the proxied MLflow server. If an alias still points at an older run whose artifact URI is a direct object-store path such as s3://..., the deploy workflow now fails early and you should re-log or re-register that model through the current MLflow deployment before rolling out serving.
To let GitHub Actions deploy MLflow automatically, also set:
- Env var
MLFLOW_DEPLOY_ENABLED=true - Env var
MLFLOW_ARTIFACT_ROOT(e.g.s3://hh-lion-bucket-engineering/mlflow/artifacts) - Optional env var
MLFLOW_AWS_REGION(defaults toautofor R2) - Optional env var
MLFLOW_WORKERS(defaults to1engineering /2production) - Optional env var
MLFLOW_HOSTNAME(overrides ingress host in the manifest) - Optional env var
MLFLOW_SERVER_ALLOWED_HOSTS(for MLflow host header protection; e.g.mlflow-engineering.hh-engineering.my.id) - Optional env var
MLFLOW_SERVER_CORS_ALLOWED_ORIGINS(for browser access; e.g.https://mlflow-engineering.hh-engineering.my.id) - Env secret
MLFLOW_BACKEND_STORE_URI(Postgres connection URI)
Serving access is configured per environment in the serving manifest:
k8s/engineering/serving.yamlis internal-only behind aClusterIPservicek8s/production/serving.yamlis internal-only behind aClusterIPservice
4. GitHub Environments Setup
Go to GitHub → Repository → Settings → Environments and create two environments:
| Environment | Protection Rules (Recommended) |
|---|---|
engineering | None (auto-deploy on push to develop) |
production | Required reviewers, deployment branch = main |
Important: The environment names must be exactly
engineeringandproduction(lowercase).
5. GitHub Repository Settings (repo-level)
These are repo-level settings (not environment-specific).
Repository Variables
GitHub → Settings → Secrets and variables → Actions → Variables (Repository variables)
| Variable Name | Description | Example |
|---|---|---|
REGISTRY_URL | DigitalOcean container registry URL | registry.digitalocean.com/hungryhub |
REGISTRY_USERNAME | DOCR login username | hungryhub-bot or your DO email |
GHCR_USERNAME | GitHub Container Registry username that owns the production PAT | hungryhub-bot |
Repository Secrets
GitHub → Settings → Secrets and variables → Actions → Repository secrets
| Secret Name | Description | Example |
|---|---|---|
DOCR_TOKEN | DigitalOcean token used for DOCR docker login | dop_v1_abc123... |
DO_API_TOKEN | DigitalOcean API token with access to the engineering DOKS cluster (used by doctl) | dop_v1_abc123... |
PAT | GitHub Personal Access Token with write:packages scope (image push) | ghp_xxxx... |
Note:
PATis used for GHCR login (production image push) and for creating theimagePullSecretin EKS. Set repository variableGHCR_USERNAMEto the exact GitHub username that owns this PAT; the workflow now fails production deploys if it is missing.
6. GitHub Environment Variables (per env)
Set these under each GitHub Environment (engineering and production separately):
GitHub → Settings → Environments → [engineering/production] → Environment variables
| Variable Name | Description | Engineering Example | Production Example |
|---|---|---|---|
ENVIRONMENT | Runtime environment identifier | engineering | production |
FORCE_MOCK_MODEL | Use mock model for serving | false | false |
GROWTHBOOK_DEFAULT_HOMEPAGE_MODEL | Fallback homepage model variant used when GrowthBook is unavailable or returns no flag value | member_two_tower | member_two_tower |
CLICKHOUSE_HOST | ClickHouse server hostname | ch-engineering.internal | ch-prod.internal |
CLICKHOUSE_PORT | ClickHouse native protocol port | 9000 | 9000 |
CLICKHOUSE_HTTP_PORT | ClickHouse HTTP protocol port (used by training/feature jobs) | 8123 | 8123 |
CLICKHOUSE_SECURE | Use TLS for ClickHouse native/HTTP clients | true | true |
CLICKHOUSE_VERIFY_CERT | Verify ClickHouse TLS certificate | true | true |
CLICKHOUSE_USER | ClickHouse username | default | recsys_app |
CLICKHOUSE_DATABASE | ClickHouse database name | liondb_engineering | liondb_production |
REDIS_HOST | Redis server hostname | redis-engineering.internal | redis-prod.internal |
REDIS_PORT | Redis port | 6379 | 6379 |
REDIS_DB | Optional Redis logical database index | 0 | 5 |
REDIS_TLS | Enable TLS for Redis connections | false | false |
OPENSEARCH_NODE | OpenSearch node URL including scheme and optional port | https://search-engineering.internal:443 | https://search-prod.internal:443 |
OPENSEARCH_INDEX | OpenSearch index or alias name used by retrieval and metadata refresh | engineering_restaurants | production_restaurants |
OPENSEARCH_VERIFY_CERTS | Verify OpenSearch TLS certificates | true | true |
OPENSEARCH_CA_CERTS | Optional CA bundle path mounted in the pod | (empty or mounted path) | /etc/ssl/certs/custom-opensearch-ca.pem |
NAMESPACE | Optional Kafka topic namespace prefix applied to topic vars at runtime | engineering | production |
KAFKA_BOOTSTRAP_SERVERS | Kafka bootstrap servers for ingestion/emission | broker1:9092,broker2:9092 | broker1:9092,broker2:9092 |
KAFKA_SECURITY_PROTOCOL | Kafka broker security protocol | sasl_ssl | ssl |
KAFKA_SASL_MECHANISM | SASL mechanism for secured brokers | SCRAM-SHA-512 | (empty) |
KAFKA_SSL_CA_LOCATION | Broker CA file path mounted in pod | /etc/hh-lion/kafka/broker-ca.crt | /etc/hh-lion/kafka/broker-ca.crt |
KAFKA_SSL_CERT_LOCATION | Broker client certificate file path mounted in pod | (empty or mounted path) | /etc/hh-lion/kafka/broker-client.crt |
KAFKA_SSL_KEY_LOCATION | Broker client key file path mounted in pod | (empty or mounted path) | /etc/hh-lion/kafka/broker-client.key |
KAFKA_SCHEMA_REGISTRY_URL | Schema Registry URL for consumer/producer contracts | https://schema-registry.engineering.internal:8081 | https://schema-registry.prod.internal:8081 |
KAFKA_SCHEMA_REGISTRY_BASIC_AUTH_CREDENTIALS_SOURCE | Schema Registry auth source | USER_INFO | USER_INFO |
KAFKA_SCHEMA_REGISTRY_SSL_CA_LOCATION | Schema Registry CA file path mounted in pod | /etc/hh-lion/kafka/schema-registry-ca.crt | /etc/hh-lion/kafka/schema-registry-ca.crt |
KAFKA_SCHEMA_REGISTRY_SSL_CERT_LOCATION | Schema Registry client cert file path mounted in pod | (empty or mounted path) | /etc/hh-lion/kafka/schema-registry-client.crt |
KAFKA_SCHEMA_REGISTRY_SSL_KEY_LOCATION | Schema Registry client key file path mounted in pod | (empty or mounted path) | /etc/hh-lion/kafka/schema-registry-client.key |
KAFKA_EVENTS_TOPIC | Base interaction events topic consumed into ClickHouse; prefixed by NAMESPACE when set | hh.lion.interaction.events | hh.lion.interaction.events |
KAFKA_CONSUMER_GROUP | Consumer group for ingestion worker | feature-store-consumer | feature-store-consumer |
KAFKA_DLQ_TOPIC | Base dead-letter topic for invalid events; prefixed by NAMESPACE when set | hh.lion.interaction.events.dlq | hh.lion.interaction.events.dlq |
KAFKA_IMPRESSION_TOPIC | Base topic for serving impression emission; prefixed by NAMESPACE when set | hh.lion.recsys.impressions | hh.lion.recsys.impressions |
KAFKA_IMPRESSION_SCHEMA_PATH | Optional path to Avro schema in runtime container; leave empty for JSON fallback | (empty) | (empty) |
MLFLOW_TRACKING_URI | MLflow tracking server URL | http://mlflow.engineering.internal | http://mlflow.prod.internal |
MLFLOW_S3_ENDPOINT_URL | S3/R2 endpoint for MLflow artifacts | https://<acct>.r2.cloudflarestorage.com | https://<acct>.r2.cloudflarestorage.com |
MLFLOW_ARTIFACT_ROOT | MLflow artifact root for the MLflow service | s3://hh-lion-bucket-engineering/mlflow/artifacts | s3://hh-lion-bucket-production/mlflow/artifacts |
MLFLOW_MODEL_NAME | Registered model name for serving | homepage_two_tower | homepage_two_tower |
MLFLOW_MODEL_ALIAS | Registered model alias for serving | engineering | production |
MLFLOW_DEPLOY_ENABLED | Whether the workflow also deploys MLflow | true | true |
MLFLOW_HOSTNAME | Override MLflow ingress host (optional) | mlflow-engineering.hh-engineering.my.id | mlflow.hungryhub.com |
MLFLOW_AWS_REGION | Artifact client region for MLflow pod (auto for R2) | auto | auto |
MLFLOW_WORKERS | MLflow gunicorn workers (optional) | 1 | 2 |
MLFLOW_SERVER_ALLOWED_HOSTS | Allowed Host headers for MLflow UI/API (optional) | mlflow-engineering.hh-engineering.my.id | mlflow.hungryhub.com |
MLFLOW_SERVER_CORS_ALLOWED_ORIGINS | Allowed browser origins for MLflow UI/API (optional) | https://mlflow-engineering.hh-engineering.my.id | https://mlflow.hungryhub.com |
SOURCE_CLICKHOUSE_HOST | Source ClickHouse host for production bootstrap backfill workflow | (not used) | 10.10.0.3 |
SOURCE_CLICKHOUSE_HTTP_PORT | Source ClickHouse HTTP port for production bootstrap backfill workflow | (not used) | 8123 |
SOURCE_CLICKHOUSE_DATABASE | Source ClickHouse database for production bootstrap backfill workflow | (not used) | booking_production |
FEAST_REGISTRY_TYPE | Feast registry backend type for shared deployments | sql | sql |
FEAST_REGISTRY_CACHE_TTL_SECONDS | Optional Feast registry cache TTL | 60 | 60 |
FEAST_REGISTRY_SQLALCHEMY_ECHO | Optional SQLAlchemy debug logging flag for Feast registry | false | false |
FEAST_REGISTRY_SQLALCHEMY_POOL_PRE_PING | Optional SQLAlchemy connection health checks for Feast registry | true | true |
FEAST_REGISTRY_THREAD_POOL_WORKERS | Optional Feast registry worker count | 4 | 4 |
LOG_LEVEL | Application log level | DEBUG | INFO |
CORS_ALLOWED_ORIGINS | Comma-separated allowed CORS origins | * | https://app.hungryhub.com |
ROLLBAR_ENABLED | Enable Rollbar error reporting for serving plus Python script, worker, and job entry points when the token is also configured and ENVIRONMENT is engineering or production | true | true |
These become a Kubernetes ConfigMap (lion-config) at deploy time.
Keep the storage roles separate when naming databases:
CLICKHOUSE_DATABASEis the analytics / feature-source database and can remainliondb_engineering/liondb_production.MLFLOW_BACKEND_STORE_URIshould point to dedicated MLflow metadata databases such asliondb_engineering/liondb_production.FEAST_REGISTRY_PATHshould point to dedicated Feast registry databases such asliondb_engineering/liondb_production.
For the current Feast architecture, set FEAST_REGISTRY_TYPE=sql and provide a shared registry DSN via the environment secrets below. The deploy workflow now applies the tracked Feast repo explicitly before rolling out serving, and the engineering bootstrap workflow reuses the same registry when refreshing features or training.
The current Feast retrieval contract is:
- Feast online store (Redis) is the required serving path for member, item, and cohort model features.
- Feast offline store (ClickHouse) is the required training and export path through v2
FeatureServicecontracts. - Training features must be point-in-time correct. Features that cannot be retrieved through v2 historical tables with
feature_timestampsemantics must be excluded from the model contract. - Direct ClickHouse reads are limited to diagnostics, validation, candidate-source support, and session features that are not part of the Feast model-feature contract. They are not an acceptable substitute for Feast online serving.
The v2 ClickHouse feature tables retain history by entity and feature_timestamp.
Feast source definitions must point only at those v2 historical tables, and online
publication must occur only after historical coverage, freshness, quality, and
recommendation-quality gates pass.
For the current deployment policy, set GROWTHBOOK_DEFAULT_HOMEPAGE_MODEL=member_two_tower
in both engineering and production so the service keeps the same fallback behavior across
environments when GrowthBook is unavailable.
Kafka Topic Naming Standard
Use the following rule for all Kafka topics managed by hh-lion:
- Effective topic format:
<NAMESPACE>.<base-topic> - Namespace format:
<environment> - Base topic format: lowercase, dot-separated, domain-first
Naming rules:
- Prefer domain-first names such as
hh.lion.recsys.impressions - Use
*.eventsfor canonical event streams such ashh.lion.interaction.events - Use
.dlqfor dead-letter topics such ashh.lion.interaction.events.dlq - Do not create new Kafka topics using
_ - Do not use camelCase in topic names
Canonical examples for this repo:
engineering.hh.lion.interaction.eventsengineering.hh.lion.interaction.events.dlqengineering.hh.lion.recsys.impressionsproduction.hh.lion.interaction.eventsproduction.hh.lion.interaction.events.dlqproduction.hh.lion.recsys.impressions
7. GitHub Environment Secrets (per env)
Set these under each GitHub Environment (engineering and production separately):
GitHub → Settings → Environments → [engineering/production] → Environment secrets
| Secret Name | Description | Notes |
|---|---|---|
CLICKHOUSE_PASSWORD | ClickHouse password | Different credentials per environment |
REDIS_PASSWORD | Redis password | Required only if your Redis instance has auth enabled (e.g., engineering) |
AWS_ACCESS_KEY_ID | R2/S3 access key for MLflow artifact download | GitHub environment secret used to populate MLflow server secrets and lion-secrets R2_ACCESS_KEY_ID |
AWS_SECRET_ACCESS_KEY | R2/S3 secret key for MLflow artifact download | GitHub environment secret used to populate MLflow server secrets and lion-secrets R2_SECRET_ACCESS_KEY |
MLFLOW_BACKEND_STORE_URI | Postgres connection URI for MLflow service metadata DB | e.g., postgresql://user:pass@host:5432/liondb_engineering?sslmode=require |
KAFKA_SASL_USERNAME | Kafka SASL username | Required for sasl_ssl / sasl_plaintext brokers |
KAFKA_SASL_PASSWORD | Kafka SASL password | Required for sasl_ssl / sasl_plaintext brokers |
KAFKA_SCHEMA_REGISTRY_USERNAME | Schema Registry basic-auth username | Optional; set with password when registry requires auth |
KAFKA_SCHEMA_REGISTRY_PASSWORD | Schema Registry basic-auth password | Optional; set with username when registry requires auth |
KAFKA_SSL_CA_CERT | Broker CA certificate PEM | Optional GitHub secret used by deploy workflow to create lion-kafka-certs |
KAFKA_SSL_CERT | Broker client certificate PEM | Optional GitHub secret used by deploy workflow to create lion-kafka-certs |
KAFKA_SSL_KEY | Broker client key PEM | Optional GitHub secret used by deploy workflow to create lion-kafka-certs |
KAFKA_SCHEMA_REGISTRY_SSL_CA_CERT | Schema Registry CA certificate PEM | Optional GitHub secret used by deploy workflow to create lion-kafka-certs |
KAFKA_SCHEMA_REGISTRY_SSL_CERT | Schema Registry client certificate PEM | Optional GitHub secret used by deploy workflow to create lion-kafka-certs |
KAFKA_SCHEMA_REGISTRY_SSL_KEY | Schema Registry client key PEM | Optional GitHub secret used by deploy workflow to create lion-kafka-certs |
FEAST_REGISTRY_PATH | Shared Feast registry SQLAlchemy write DSN | Required for shared Feast registry bootstrapping in deploy/bootstrap workflows; e.g. postgresql://user:pass@host:5432/liondb_engineering?sslmode=require |
FEAST_REGISTRY_READ_PATH | Optional Feast registry read-replica SQLAlchemy DSN | Optional; only set together with FEAST_REGISTRY_PATH, for example a read-only replica of liondb_engineering |
CACHE_HMAC_SECRET | HMAC secret for cache key hashing | Generate with openssl rand -hex 32 |
OPENSEARCH_USER | OpenSearch basic auth username (optional if auth disabled) | search_user |
OPENSEARCH_PASSWORD | OpenSearch basic auth password | Stored as GitHub secret |
ROLLBAR_ACCESS_TOKEN | Rollbar post_server_item access token | Use separate tokens per environment when possible |
The deploy workflow mounts optional Kafka certificate material from the lion-kafka-certs
secret into /etc/hh-lion/kafka for both serving and consumer pods. Set the *_LOCATION
variables above to those in-container paths and provide the corresponding PEM contents through
GitHub environment secrets when SSL or mTLS is required.
In addition, set these infrastructure variables per environment:
Engineering environment variables
| Variable Name | Description | Example |
|---|---|---|
DO_CLUSTER_ID_ENGINEERING | DigitalOcean DOKS cluster ID | a1b2c3d4-e5f6-... |
Production environment variables
| Variable Name | Description | Example |
|---|---|---|
AWS_ROLE_ARN | IAM role ARN for OIDC | arn:aws:iam::123456789012:role/hh-lion-deploy |
AWS_REGION | AWS region of EKS cluster | ap-southeast-1 |
EKS_CLUSTER_NAME | EKS cluster name | hungryhub-prod |
8. AWS OIDC Setup (Production)
Production uses OIDC federation instead of static AWS access keys. This is more secure — no long-lived credentials stored in GitHub.
Step 1: Create OIDC Identity Provider in AWS IAM
- Go to AWS IAM → Identity providers → Add provider
- Set:
- Provider type: OpenID Connect
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
- Click Add provider
Step 2: Create IAM Role
- Go to IAM → Roles → Create role
- Choose Web identity
- Select the OIDC provider you just created
- Set the trust policy (replace
YOUR_GITHUB_ORGandYOUR_REPO):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:hungryhub-team/hh-lion:environment:production"
}
}
}
]
}
Step 3: Attach Policies to the Role
Attach these AWS managed policies (or more restrictive custom ones):
AmazonEKSClusterPolicy— or a custom policy scoped to your cluster- A custom policy for EKS access:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"eks:DescribeCluster",
"eks:ListClusters"
],
"Resource": "arn:aws:eks:ap-southeast-1:YOUR_ACCOUNT_ID:cluster/YOUR_CLUSTER_NAME"
}
]
}
Step 4: Configure EKS RBAC
The IAM role also needs Kubernetes RBAC permissions. Edit the aws-auth ConfigMap or use EKS access entries:
# Option A: aws-auth ConfigMap
kubectl edit configmap aws-auth -n kube-system
Add under mapRoles:
- rolearn: arn:aws:iam::YOUR_ACCOUNT_ID:role/hh-lion-deploy
username: hh-lion-deploy
groups:
- hh-lion-deployers # avoid system:masters; bind this group to least-privilege RBAC
Step 5: Save the Role ARN
Copy the role ARN and set it as AWS_ROLE_ARN in the production environment variables (see Section 7).
9. Create Kubernetes Namespace
On both clusters, create the namespace before the first deploy:
# Engineering (connect to DOKS first)
doctl kubernetes cluster kubeconfig save YOUR_CLUSTER_ID
kubectl create namespace hh-lion
# Production (connect to EKS first)
aws eks update-kubeconfig --name YOUR_CLUSTER --region ap-southeast-1
kubectl create namespace hh-lion
The deploy workflow creates the
lion-configConfigMap andlion-secretsSecret automatically on each deploy. You only need to create the namespace manually once.
10. Trigger a Deployment
Engineering (automatic)
Push or merge to the develop branch:
git checkout develop
git push origin develop
The workflow triggers automatically and deploys to engineering.
Production (manual)
- Go to GitHub → Actions → “Build & Deploy to Kubernetes”
- Click “Run workflow”
- Select environment: production
- Click “Run workflow”
The workflow will:
- Build the Docker image from
recsys/Dockerfile - Push to the appropriate registry (DOCR or GHCR)
- Patch the K8s manifest with the new image tag
- Create/update the ConfigMap (
lion-config) and Secret (lion-secrets) - Apply the K8s manifests
- Wait for rollout to complete
11. Verify Deployment
After the workflow completes:
# Check pods are running
kubectl get pods -n hh-lion
# Check the deployed image
kubectl get deployment hh-lion-api -n hh-lion -o jsonpath='{.spec.template.spec.containers[0].image}'
# Check health endpoint (port-forward for testing)
kubectl port-forward svc/hh-lion 8000:80 -n hh-lion
curl http://localhost:8000/health
12. Troubleshooting
Pod in CrashLoopBackOff
kubectl logs deployment/hh-lion-api -n hh-lion --previous
Common causes:
- Missing or incorrect env vars in ConfigMap/Secret
- ClickHouse or Redis not reachable from the cluster
- Invalid
MLFLOW_MODEL_NAMEorMLFLOW_MODEL_ALIAS— model version or artifacts not found
ImagePullBackOff
kubectl describe pod -l app=hh-lion -n hh-lion | grep -A5 Events
Common causes:
- Engineering: DOCR token expired or registry URL wrong
- Production:
hh-lion-ghcr-secretnot created or PAT expired
Workflow Fails at “Configure AWS credentials”
- Verify the OIDC provider is set up in AWS IAM
- Check the trust policy matches
repo:hungryhub-team/hh-lion:environment:production - Ensure
AWS_ROLE_ARNenvironment variable is correct
ConfigMap/Secret Not Updating
The deploy workflow uses --dry-run=client -o yaml | kubectl apply -f - for idempotent updates. If values seem stale:
# Check current configmap values
kubectl get configmap lion-config -n hh-lion -o yaml
# Check secrets exist (values are base64-encoded)
kubectl get secret lion-secrets -n hh-lion -o yaml
Quick Reference: All GitHub Settings
Repository Variables (repo-level)
| Variable | Value |
|---|---|
REGISTRY_URL | registry.digitalocean.com/hungryhub |
REGISTRY_USERNAME | DOCR username |
Repository Secrets (repo-level)
| Secret | Value |
|---|---|
DOCR_TOKEN | DigitalOcean token for registry login |
DO_API_TOKEN | DigitalOcean API token for doctl / DOKS access |
PAT | GitHub PAT (write:packages) |
Engineering Environment — Variables
| Variable | Value |
|---|---|
ENVIRONMENT | engineering |
FORCE_MOCK_MODEL | false |
CLICKHOUSE_HOST | Your engineering ClickHouse host |
CLICKHOUSE_PORT | 9000 |
CLICKHOUSE_HTTP_PORT | 8123 |
CLICKHOUSE_SECURE | true |
CLICKHOUSE_VERIFY_CERT | true |
CLICKHOUSE_USER | Engineering ClickHouse username |
CLICKHOUSE_DATABASE | liondb_engineering |
REDIS_HOST | Your engineering Redis host |
REDIS_PORT | 6379 |
REDIS_DB | 0 |
REDIS_TLS | false |
NAMESPACE | engineering |
KAFKA_BOOTSTRAP_SERVERS | Engineering Kafka bootstrap servers |
KAFKA_SECURITY_PROTOCOL | sasl_ssl |
KAFKA_SASL_MECHANISM | e.g. SCRAM-SHA-512 |
KAFKA_SSL_CA_LOCATION | /etc/hh-lion/kafka/broker-ca.crt |
KAFKA_SCHEMA_REGISTRY_URL | Engineering Schema Registry URL |
KAFKA_SCHEMA_REGISTRY_BASIC_AUTH_CREDENTIALS_SOURCE | USER_INFO |
KAFKA_SCHEMA_REGISTRY_SSL_CA_LOCATION | /etc/hh-lion/kafka/schema-registry-ca.crt |
KAFKA_EVENTS_TOPIC | hh.lion.interaction.events |
KAFKA_CONSUMER_GROUP | feature-store-consumer |
KAFKA_DLQ_TOPIC | hh.lion.interaction.events.dlq |
KAFKA_IMPRESSION_TOPIC | hh.lion.recsys.impressions |
KAFKA_IMPRESSION_SCHEMA_PATH | Optional; only set when schema is mounted in runtime container |
MLFLOW_TRACKING_URI | Your engineering MLflow URL |
MLFLOW_S3_ENDPOINT_URL | Your engineering R2/S3 endpoint |
MLFLOW_MODEL_NAME | Registered model name for engineering serving |
MLFLOW_MODEL_ALIAS | Registered model alias for engineering serving |
FEAST_REGISTRY_TYPE | sql |
LOG_LEVEL | DEBUG |
CORS_ALLOWED_ORIGINS | * |
ROLLBAR_ENABLED | true |
DO_CLUSTER_ID_ENGINEERING | DigitalOcean cluster ID |
Engineering Environment — Secrets
| Secret | Value |
|---|---|
CLICKHOUSE_PASSWORD | Engineering ClickHouse password |
REDIS_PASSWORD | Engineering Redis password (if enabled) |
AWS_ACCESS_KEY_ID | Engineering R2/S3 artifact access key (copied by deploy workflow into lion-secrets as R2_ACCESS_KEY_ID) |
AWS_SECRET_ACCESS_KEY | Engineering R2/S3 artifact secret key (copied by deploy workflow into lion-secrets as R2_SECRET_ACCESS_KEY) |
MLFLOW_BACKEND_STORE_URI | e.g. postgresql://user:pass@host:5432/liondb_engineering?sslmode=require |
FEAST_REGISTRY_PATH | e.g. postgresql://user:pass@host:5432/liondb_engineering?sslmode=require |
KAFKA_SASL_USERNAME | Engineering Kafka SASL username |
KAFKA_SASL_PASSWORD | Engineering Kafka SASL password |
KAFKA_SCHEMA_REGISTRY_USERNAME | Engineering Schema Registry username (if required) |
KAFKA_SCHEMA_REGISTRY_PASSWORD | Engineering Schema Registry password (if required) |
KAFKA_SSL_CA_CERT | Broker CA certificate PEM for mounted secret |
KAFKA_SCHEMA_REGISTRY_SSL_CA_CERT | Schema Registry CA certificate PEM for mounted secret |
CACHE_HMAC_SECRET | openssl rand -hex 32 |
ROLLBAR_ACCESS_TOKEN | Engineering Rollbar post_server_item access token |
Production Environment — Variables
| Variable | Value |
|---|---|
ENVIRONMENT | production |
FORCE_MOCK_MODEL | false |
CLICKHOUSE_HOST | Your production ClickHouse host |
CLICKHOUSE_PORT | 9000 |
CLICKHOUSE_HTTP_PORT | 8123 |
CLICKHOUSE_SECURE | true |
CLICKHOUSE_VERIFY_CERT | true |
CLICKHOUSE_USER | Production ClickHouse username |
CLICKHOUSE_DATABASE | liondb_production |
REDIS_HOST | Your production Redis host |
REDIS_PORT | 6379 |
REDIS_DB | 5 |
REDIS_TLS | false |
NAMESPACE | production |
KAFKA_BOOTSTRAP_SERVERS | Production Kafka bootstrap servers |
KAFKA_SECURITY_PROTOCOL | ssl |
KAFKA_SSL_CA_LOCATION | /etc/hh-lion/kafka/broker-ca.crt |
KAFKA_SSL_CERT_LOCATION | /etc/hh-lion/kafka/broker-client.crt |
KAFKA_SSL_KEY_LOCATION | /etc/hh-lion/kafka/broker-client.key |
KAFKA_SCHEMA_REGISTRY_URL | Production Schema Registry URL |
KAFKA_SCHEMA_REGISTRY_BASIC_AUTH_CREDENTIALS_SOURCE | USER_INFO |
KAFKA_SCHEMA_REGISTRY_SSL_CA_LOCATION | /etc/hh-lion/kafka/schema-registry-ca.crt |
KAFKA_SCHEMA_REGISTRY_SSL_CERT_LOCATION | /etc/hh-lion/kafka/schema-registry-client.crt |
KAFKA_SCHEMA_REGISTRY_SSL_KEY_LOCATION | /etc/hh-lion/kafka/schema-registry-client.key |
KAFKA_EVENTS_TOPIC | hh.lion.interaction.events |
KAFKA_CONSUMER_GROUP | feature-store-consumer |
KAFKA_DLQ_TOPIC | hh.lion.interaction.events.dlq |
KAFKA_IMPRESSION_TOPIC | hh.lion.recsys.impressions |
KAFKA_IMPRESSION_SCHEMA_PATH | Optional; only set when schema is mounted in runtime container |
MLFLOW_TRACKING_URI | Your production MLflow URL |
MLFLOW_S3_ENDPOINT_URL | Your production R2/S3 endpoint |
MLFLOW_MODEL_NAME | Registered model name for production serving |
MLFLOW_MODEL_ALIAS | Registered model alias for production serving |
FEAST_REGISTRY_TYPE | sql |
LOG_LEVEL | INFO |
CORS_ALLOWED_ORIGINS | https://app.hungryhub.com |
ROLLBAR_ENABLED | true |
AWS_ROLE_ARN | IAM role ARN for OIDC |
AWS_REGION | e.g., ap-southeast-1 |
EKS_CLUSTER_NAME | EKS cluster name |
Production Environment — Secrets
| Secret | Value |
|---|---|
CLICKHOUSE_PASSWORD | Production ClickHouse password |
REDIS_PASSWORD | Production Redis password (optional; leave unset/empty if no auth) |
AWS_ACCESS_KEY_ID | Production R2/S3 artifact access key (copied by deploy workflow into lion-secrets as R2_ACCESS_KEY_ID) |
AWS_SECRET_ACCESS_KEY | Production R2/S3 artifact secret key (copied by deploy workflow into lion-secrets as R2_SECRET_ACCESS_KEY) |
MLFLOW_BACKEND_STORE_URI | e.g. postgresql://user:pass@host:5432/liondb_production?sslmode=require |
FEAST_REGISTRY_PATH | e.g. postgresql://user:pass@host:5432/liondb_production?sslmode=require |
KAFKA_SCHEMA_REGISTRY_USERNAME | Production Schema Registry username (if required) |
KAFKA_SCHEMA_REGISTRY_PASSWORD | Production Schema Registry password (if required) |
KAFKA_SSL_CA_CERT | Broker CA certificate PEM for mounted secret |
KAFKA_SSL_CERT | Broker client certificate PEM for mounted secret |
KAFKA_SSL_KEY | Broker client key PEM for mounted secret |
KAFKA_SCHEMA_REGISTRY_SSL_CA_CERT | Schema Registry CA certificate PEM for mounted secret |
KAFKA_SCHEMA_REGISTRY_SSL_CERT | Schema Registry client certificate PEM for mounted secret |
KAFKA_SCHEMA_REGISTRY_SSL_KEY | Schema Registry client key PEM for mounted secret |
CACHE_HMAC_SECRET | openssl rand -hex 32 |
ROLLBAR_ACCESS_TOKEN | Production Rollbar post_server_item access token |