DevLake — Architecture and Operations
DevLake provides DORA metrics and per-squad PR cycle time for the HungryHub team (~25 devs).
- Repo: hungryhub-team/hungryhub-devlake
- URL: https://devlake.hungryhub.com (Cloudflare Tunnel)
- Dashboards: grafana.hungryhub.com (Aiven Grafana — external)
- Version: Apache DevLake v1.0.3-beta12
Architecture
Internet
│
▼
Cloudflare Tunnel (cloudflared)
│
└──► config-ui (port 4000) — DevLake admin UI
│ proxies /api/* → devlake :8080
▼
devlake (port 8080) — pipeline engine + REST API
│
▼
Aiven MySQL (managed)
hh-development project
devlake-mysql
google-europe-north1 (Finland)
Host: Hetzner CPX42 VPS (devlake-prod), Helsinki — 62.238.41.27
No local MySQL or Grafana containers — both are replaced by Aiven managed services.
Infrastructure details
| Resource | Value |
|---|---|
| Hetzner server | devlake-prod, CPX42 (8 vCPU / 16 GB / 240 GB NVMe), hel1 |
| IP | 62.238.41.27 |
| Firewall | devlake-firewall (id: 11074343) — SSH inbound only, all outbound open |
| Aiven MySQL | devlake-mysql, startup-4, google-europe-north1, project hh-development |
| Aiven MySQL host | devlake-mysql-hh-development.f.aivencloud.com:15939 |
| Aiven Grafana | public-grafana-hh-production.f.aivencloud.com, project hh-production |
| Cloudflare Tunnel | devlake-prod, id 371e5df5-0fab-4b37-9e39-23670ed00577 |
| CF CNAME | devlake.hungryhub.com → 371e5df5...cfargotunnel.com (proxied) |
| CF account id | 6bacbc90386bf7634898c165b7dbc611 |
| CF zone id | 104b9a2a601c57619abee0706a54b28a |
Key components
| Component | Description |
|---|---|
devlake container | Apache DevLake v1.0.3-beta12 — pipeline engine, REST API on :8080 |
config-ui container | Web UI on :4000 — proxies /api/* → devlake :8080 |
cloudflared container | Cloudflare Tunnel client — exposes config-ui externally without open ports |
| Aiven MySQL | Managed startup-4 plan, databases lake and lake_test |
| Aiven Grafana | External Grafana with 11 DORA dashboards in DevLake DORA folder |
Secrets (AWS SSM — prod account 202255947274, ap-southeast-1)
| SSM path | Purpose |
|---|---|
/devlake/encryption-secret | DevLake DB encryption key — never regenerate on a live instance |
/devlake/cloudflare-tunnel-token | Cloudflare Tunnel auth token |
/devlake/db-url | Aiven MySQL connection string (with tls=skip-verify) |
/devlake/e2e-db-url | E2E test DB connection string |
/devlake/cloudflare-api-token | CF account-level API token |
/devlake/github-apps/hh-devlake-1 | GitHub App creds (app_id, installation_id, private_key) for rate-limit distribution |
/devlake/github-apps/hh-devlake-2 | GitHub App creds — see DevLake GitHub Apps Rate-Limit Distribution |
/devlake/github-apps/hh-devlake-3 | GitHub App creds |
/devlake/github-apps/hh-devlake-4 | GitHub App creds |
/devlake/github-apps/hh-devlake-5 | GitHub App creds |
Pull secrets to the VPS before deploying:
./deploy/pull-secrets.sh
DB URL format:
mysql://user:pass@host:port/db?charset=utf8mb4&parseTime=True&loc=UTC&tls=skip-verifyAlways write the DB URL via Pythonre.sub— neversed, as&in the value causes shell expansion corruption.
IAM
IAM user devlake-vps (key AKIAS6F3E2YFCI6SBIPK) in the prod account has a narrow inline policy:
ssm:GetParameter+kms:Decryptscoped toarn:aws:ssm:ap-southeast-1:202255947274:parameter/devlake/*
Credentials live at /root/.aws/credentials on the VPS.
GitHub connector configuration
| Item | Value |
|---|---|
| Connection name | hungryhub-team (id: 1) |
| Endpoint | https://api.github.com/ |
| Auth method | AccessToken — 5 comma-separated installation tokens (round-robin, 25,000 req/hr) |
| Enable GraphQL | true |
| Rate limit | 20,000 req/hr |
| Scope config | hungryhub-dora (id: 1) |
Tokens are minted from 5 org-owned GitHub Apps by a cron on the VPS every 30 min. See DevLake GitHub Apps Rate-Limit Distribution for the full setup.
Scope config hungryhub-dora:
deploymentPattern:(?i)(deploy|release|prod)productionPattern:(?i)(prod|main|master|cloudflare_zone_env)— includes Terraform CF deploysprBodyClosePattern:(?i)(fix|close|resolve)(\s*)(?:#)(\d+)
15 tracked repos (all with scopeConfigId: 1):
| Repo | GitHub ID |
|---|---|
| hungryhub-team/hh-server | 199970072 |
| hungryhub-team/hh-android | 219383404 |
| hungryhub-team/hh-menu | 494021211 |
| hungryhub-team/hh-nuxt | 496500431 |
| hungryhub-team/hh-menu-frontend-new | 510972874 |
| hungryhub-team/book-bite | 539788675 |
| hungryhub-team/hh-pegasus | 733067847 |
| hungryhub-team/hh-felidae | 1006944319 |
| hungryhub-team/hh-vendors-hub | 1006956323 |
| hungryhub-team/hungryhub-terraform | 1028203938 |
| hungryhub-team/hh-lion | 1077453560 |
| hungryhub-team/eagle-eye | 1156890442 |
| hungryhub-team/hunger-games | 1158898105 |
| hungryhub-team/hh-pegasus-monorepo | 1204792079 |
| hungryhub-team/hh-relay | 1239594970 |
Projects and blueprints
Four NORMAL-mode blueprints run nightly at 00:00 UTC, one per squad:
| Blueprint ID | Name | Project | Squad | Repos |
|---|---|---|---|---|
| 10 | growth-Blueprint | growth | Growth | hh-pegasus-monorepo, hh-pegasus, book-bite, hh-lion, hh-nuxt |
| 11 | integrations-Blueprint | integrations | Integrations | hh-server, hh-android, hh-menu, hh-menu-frontend-new, hh-vendors-hub |
| 12 | automation-Blueprint | automation | Automation | hh-relay, hh-felidae, eagle-eye, hunger-games |
| 13 | platform-Blueprint | platform | Platform | hungryhub-terraform |
All share scope config hungryhub-dora (id: 1, GitHub connection 1).
To add or move a repo: update the blueprint’s scope list via the DevLake API (PATCH /blueprints/<id>). The squad source of truth is hh-relay/squad_channels.yml.
See DevLake DORA Metrics Setup for detailed configuration, debugging, and known limitations.
Grafana dashboards
Grafana folder: DevLake DORA (uid: ffo08wszkd2wwf)
Datasource: devlake (uid: dfo08jdx29z40f, MySQL, id: 15)
11 dashboards imported from official DevLake v1.0.3-beta12 JSONs:
- DORA, DORAByTeam, DORADebug
- DORADetails ×4
- EngineeringOverview, EngineeringThroughputAndCycleTime
- GitHub, Homepage
Common operations
Check container status
ssh root@62.238.41.27
cd /opt/devlake
docker compose ps
docker compose logs -f devlake
Deploy / update
# Initial deploy
./deploy/deploy.sh
# Update (pull new images, restart)
./deploy/update.sh
Trigger a manual pipeline run
Via the DevLake API (use DEVLAKE_API_TOKEN from .env):
# Trigger nightly sync for a project (replace 10-13 for other projects)
# 10=growth, 11=integrations, 12=automation, 13=platform
curl -X POST https://devlake.hungryhub.com/api/blueprints/10/trigger \
-H "Authorization: Bearer <DEVLAKE_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{}'
SSH onto the VPS
ssh root@62.238.41.27
Update DevLake version
- Edit
docker-compose.yml— bumpdevlakeandconfig-uiimage tags. - Run
./deploy/update.sh. - Verify on https://devlake.hungryhub.com.
Database operations
Check disk usage
avn service get devlake-mysql --project hh-development --format json | \
python3 -c "import json,sys; svc=json.load(sys.stdin); print(svc.get('disk_space_mb'), 'MB disk')"
Scale up if > 60% full:
avn service update devlake-mysql --plan startup-8 --project hh-development
Restore from Aiven backup
- Go to Aiven Console →
hh-development→devlake-mysql→ Backups. - Fork to a new service using the desired backup point.
- Export:
mysqldump -h <new-host> -u avnadmin -p lake > lake_backup.sql - Import:
mysql -h <live-host> -u avnadmin -p lake < lake_backup.sql - Restart:
docker compose -f /opt/devlake/docker-compose.yml restart
ENCRYPTION_SECRET — do not rotate casually
All connector credentials stored in the DevLake DB are encrypted with this key. Rotating it without re-entering every connector secret will break all data pipelines.
Only rotate if the key is compromised:
openssl rand -base64 32— generate new secretaws ssm put-parameter --profile prod --region ap-southeast-1 --name /devlake/encryption-secret --type SecureString --value '<new>' --overwrite./deploy/pull-secrets.sh- Restart containers
- Re-enter all connector API tokens in the config-ui
Infrastructure provisioning scripts
| Script | Purpose |
|---|---|
deploy/provision-vps.sh | Creates Hetzner VPS |
deploy/install-docker.sh | Installs Docker on VPS |
deploy/pull-secrets.sh | Fetches SSM secrets → /opt/devlake/.env |
deploy/deploy.sh | First-time compose up |
deploy/update.sh | Pull + restart on version bump |
ops/aiven/ | Aiven MySQL provisioning |
ops/cloudflare/ | Cloudflare Tunnel creation |
ops/aws/ | IAM user for SSM access |
Related
- Epic: hungryhub-team/hungryhub-terraform#195
- Dashboards: grafana.hungryhub.com
- Runbook: DevLake_Runbook.md