Skip to content

Staging Deployment — Exto Console

Environment-specific deployment guide for Exto staging. Follow the phases in azure-deployment.md — this document provides staging-specific values, naming, and commands.

Environment Summary

PropertyValue
Environmentexto-stg
Resource Groupgg-ex-stg-console
Locationcentralus
Console URLhttps://console-stg.exto360.com
Zitadel Issuerhttps://id-stg.exto360.com
PostgreSQL Serverconsole-stg-pg
Container Apps Envconsole-stg-env
Managed Identityconsole-stg-id
Front Door Profileconsole-stg-fd
Storage Account (SPA)extoconsolewebstg
ACRgaeadev.azurecr.io
Log Leveldebug
Development Modetrue

Scaling (Low)

ServiceMinMaxNotes
Console API12Single replica is fine
Console Worker11Fixed
Zitadel11Fixed

No zone redundancy. No HA on PostgreSQL. GeneralPurpose SKU (Standard_D2ds_v4) with 128 GB storage.


Step-by-Step

1. Prepare secrets

bash
cp infra/.env.exto-stg.example infra/.env.exto-stg
# Fill in all values — see infra/.env.exto-stg.example for the template

2. Build and push images

bash
make docker-all REGISTRY=gaeadev.azurecr.io TAG=dev-latest
az acr login -n gaeadev --subscription <GaeaGlobal-subscription-id>
docker push gaeadev.azurecr.io/console-api:dev-latest
docker push gaeadev.azurecr.io/console-worker:dev-latest
docker push gaeadev.azurecr.io/console-web:dev-latest

3. Deploy infrastructure

bash
./infra/deploy.sh exto-stg --phase infra

4. Database setup

bash
# Allow your IP
az postgres flexible-server firewall-rule create \
  --resource-group gg-ex-stg-console \
  --name console-stg-pg \
  --rule-name AllowMyIP \
  --start-ip-address <YOUR_IP> \
  --end-ip-address <YOUR_IP>

# Connect
psql "postgresql://consoleadmin:<POSTGRES_ADMIN_PASSWORD>@console-stg-pg.postgres.database.azure.com:5432/postgres?sslmode=require"
sql
CREATE ROLE zitadel LOGIN PASSWORD '<ZITADEL_DB_PASSWORD>';
ALTER DATABASE zitadel_auth OWNER TO zitadel;

CREATE ROLE console_app LOGIN PASSWORD '<CONSOLE_DB_PASSWORD>';
GRANT azure_pg_admin TO console_app;
CREATE DATABASE console OWNER console_app;
\c console
GRANT ALL PRIVILEGES ON SCHEMA public TO console_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO console_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO console_app;
\q
bash
# Remove firewall rule
az postgres flexible-server firewall-rule delete \
  --resource-group gg-ex-stg-console \
  --name console-stg-pg \
  --rule-name AllowMyIP --yes

5. Zitadel init (local Docker)

See Phase 4 in azure-deployment.md for the full Docker command. Use these staging-specific values:

VariableStaging Value
ZITADEL_EXTERNALDOMAINid-stg.exto360.com
ZITADEL_DATABASE_POSTGRES_HOSTconsole-stg-pg.postgres.database.azure.com
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_EMAIL_ADDRESSadmin@exto360.com
ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_FROMconsole-notifications@exto360.com

Extract PAT tokens from the init log and add ZITADEL_LOGIN_SERVICE_TOKEN to infra/.env.exto-stg.

6. Deploy Zitadel + Front Door

bash
./infra/deploy.sh exto-stg --phase zitadel_frontdoor

7. Configure DNS

Retrieve values:

bash
ASUID=$(az containerapp env show -n console-stg-env -g gg-ex-stg-console \
  --query "properties.customDomainConfiguration.customDomainVerificationId" -o tsv)

FD_ID_HOST=$(az afd endpoint list --profile-name console-stg-fd -g gg-ex-stg-console \
  --query "[?starts_with(name,'id-')].hostName" -o tsv)

DNSAUTH_ID=$(az afd custom-domain show --profile-name console-stg-fd -g gg-ex-stg-console \
  --custom-domain-name id-stg-exto360-com \
  --query "validationProperties.validationToken" -o tsv)

Create DNS records:

TypeNameValue
TXTasuid.id-stg.exto360.com<customDomainVerificationId>
TXT_dnsauth.id-stg.exto360.com<validationToken>
CNAMEid-stg.exto360.com<id-endpoint>.azurefd.net
TXTasuid.console-stg.exto360.com<customDomainVerificationId>

Verify:

bash
curl -s https://id-stg.exto360.com/debug/ready
# Expected: ok

8. Bootstrap

bash
cat > .bootstrap-input.env << 'EOF'
ZITADEL_URL=https://id-stg.exto360.com
ZITADEL_PAT=<admin-human-user-pat>
CONSOLE_URL=https://console-stg.exto360.com
DEVELOPMENT_MODE=true
EOF

go run ./scripts/bootstrap/

Copy output values from .bootstrap.env to infra/.env.exto-stg.

9. Full stack deploy

bash
./infra/deploy.sh exto-stg

10. Console DNS + verify

Retrieve console Front Door values:

bash
FD_CONSOLE_HOST=$(az afd endpoint list --profile-name console-stg-fd -g gg-ex-stg-console \
  --query "[?starts_with(name,'console-')].hostName" -o tsv)

DNSAUTH_CONSOLE=$(az afd custom-domain show --profile-name console-stg-fd -g gg-ex-stg-console \
  --custom-domain-name console-stg-exto360-com \
  --query "validationProperties.validationToken" -o tsv)
TypeNameValue
CNAMEconsole-stg.exto360.com<console-endpoint>.azurefd.net
TXT_dnsauth.console-stg.exto360.com<validationToken>
bash
curl -s https://console-stg.exto360.com/api/healthz

11. Deploy Web SPA

bash
cd web && npm run build && cd ..
./infra/deploy.sh exto-stg --deploy-web-only

12. Re-run bootstrap (webhook)

bash
go run ./scripts/bootstrap/
./infra/deploy.sh exto-stg   # pick up webhook secret if changed

Day-to-Day Operations

Redeploy after code changes

bash
make docker-all REGISTRY=gaeadev.azurecr.io TAG=dev-latest
docker push gaeadev.azurecr.io/console-api:dev-latest
docker push gaeadev.azurecr.io/console-worker:dev-latest
./infra/deploy.sh exto-stg

SPA-only update

bash
cd web && npm run build && cd ..
./infra/deploy.sh exto-stg --deploy-web-only

Deploy specific image tag

bash
./infra/deploy.sh exto-stg --image-tag git-abc1234

Preview changes

bash
./infra/deploy.sh exto-stg --what-if