From ef793a94bf69d5baff4809c4d497ff9d09ac7508 Mon Sep 17 00:00:00 2001 From: Jan Librowski Date: Thu, 1 Oct 2026 16:12:51 +0200 Subject: [PATCH 1/2] chore(deploy): cap the bundled Temporal server at 2 CPUs and 1.5 GB The deploy workflow now ships this compose to the AI Studio VM, replacing the hand-maintained file that carried these limits. --- deploy/ai-studio/docker-compose.override.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/ai-studio/docker-compose.override.yml b/deploy/ai-studio/docker-compose.override.yml index fd8e4067a..3176d2c6c 100644 --- a/deploy/ai-studio/docker-compose.override.yml +++ b/deploy/ai-studio/docker-compose.override.yml @@ -33,6 +33,11 @@ services: POSTGRES_USER: temporal POSTGRES_PWD: ${TEMPORAL_DB_PASSWORD:-temporal} POSTGRES_SEEDS: temporal-db + deploy: + resources: + limits: + cpus: '2.0' + memory: 1.5G restart: unless-stopped # Inspects this bundled cluster only. An external cluster comes with its own UI. From 38f113fb21eac6e351db3c689111fbd8596f81d1 Mon Sep 17 00:00:00 2001 From: Jakub Kubacki Date: Fri, 2 Oct 2026 13:29:54 +0200 Subject: [PATCH 2/2] chore(deploy): generate the VM .env from GitHub secrets/vars, add resource limits --- .github/workflows/deploy-ai-studio.yml | 42 ++++++++++++-------- deploy/ai-studio/docker-compose.override.yml | 10 +++++ deploy/ai-studio/docker-compose.yml | 20 ++++++++++ 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/.github/workflows/deploy-ai-studio.yml b/.github/workflows/deploy-ai-studio.yml index 9adba0961..6e0c02272 100644 --- a/.github/workflows/deploy-ai-studio.yml +++ b/.github/workflows/deploy-ai-studio.yml @@ -90,35 +90,43 @@ jobs: # The VM runs the repo's compose files, shipped here on every deploy (base64, # so the script stays free of quoting). Compose is run from the project # directory, not with -f: that is what applies docker-compose.override.yml - # by default and honours COMPOSE_FILE from the VM's .env. + # by default. # - # The retired-key check runs before anything is written, so a refused deploy - # leaves the VM exactly as it was. It lives here rather than in the compose - # file because Compose 2.21 and older evaluate a nested `${A:+${B:?}}` guard - # eagerly and fail on every command, key set or not. - # - # The image tags are written into that .env rather than exported: an export - # dies with this shell, and the next `docker compose up -d worker` on the VM - # would fall back to the local ai-studio-* names. Only the two image lines - # are replaced; the rest of .env is the VM's own and stays untouched. + # .env is generated in full from the repo secrets/vars on every deploy, + # so nothing on the VM is edited by hand. It holds the image tags too: an + # export dies with this shell, and the next `docker compose up -d worker` + # on the VM would fall back to the local ai-studio-* names. - name: Refresh docker compose on Azure VM env: IMAGE: ${{ env.REGISTRY }}/${{ env.APP }}:${{ needs.build-and-push.outputs.image_tag }} + # repo-level secrets for credentials, vars for the rest — no `environment:`, + # which would change the OIDC subject the Azure federated credential trusts + AI_API_KEY: ${{ secrets.AI_API_KEY }} + TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} + APP_DB_PASSWORD: ${{ secrets.APP_DB_PASSWORD }} + TEMPORAL_DB_PASSWORD: ${{ secrets.TEMPORAL_DB_PASSWORD }} + AI_BASE_URL: ${{ vars.AI_BASE_URL }} + AI_MODEL: ${{ vars.AI_MODEL }} + RATE_LIMIT_EXECUTE_PER_MINUTE: ${{ vars.RATE_LIMIT_EXECUTE_PER_MINUTE || '10' }} + RATE_LIMIT_EXECUTE_PER_DAY: ${{ vars.RATE_LIMIT_EXECUTE_PER_DAY || '50' }} run: | + # the databases keep the password they were created with — an empty one + # would fall back to the compose default and lock the apps out + : "${APP_DB_PASSWORD:?set secret APP_DB_PASSWORD}" "${TEMPORAL_DB_PASSWORD:?set secret TEMPORAL_DB_PASSWORD}" + # .env is generated in full on every deploy; single quotes keep values literal + ENV_B64=$(for k in AI_API_KEY AI_BASE_URL AI_MODEL TAVILY_API_KEY \ + RATE_LIMIT_EXECUTE_PER_MINUTE RATE_LIMIT_EXECUTE_PER_DAY \ + APP_DB_PASSWORD TEMPORAL_DB_PASSWORD; do + printf "%s='%s'\n" "$k" "${!k}" + done | cat - <(printf "RUNTIME_IMAGE='%s'\nWEB_IMAGE='%s'\n" "$IMAGE-runtime" "$IMAGE-web") | base64 -w0) COMPOSE_B64=$(base64 -w0 deploy/ai-studio/docker-compose.yml) OVERRIDE_B64=$(base64 -w0 deploy/ai-studio/docker-compose.override.yml) SCRIPT=$(cat < docker-compose.yml echo "$OVERRIDE_B64" | base64 -d > docker-compose.override.yml - touch .env - { grep -vE '^(RUNTIME_IMAGE|WEB_IMAGE)=' .env || true; printf 'RUNTIME_IMAGE=%s\nWEB_IMAGE=%s\n' "$IMAGE-runtime" "$IMAGE-web"; } > .env.tmp - chmod --reference=.env .env.tmp && chown --reference=.env .env.tmp && mv .env.tmp .env + (umask 077; echo "$ENV_B64" | base64 -d > .env) az acr login --name synergycodes docker compose pull docker compose up -d --no-build --force-recreate --remove-orphans diff --git a/deploy/ai-studio/docker-compose.override.yml b/deploy/ai-studio/docker-compose.override.yml index 3176d2c6c..4715bc723 100644 --- a/deploy/ai-studio/docker-compose.override.yml +++ b/deploy/ai-studio/docker-compose.override.yml @@ -18,6 +18,11 @@ services: interval: 5s timeout: 3s retries: 12 + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M restart: unless-stopped # auto-setup is dev-grade; sustained load should move to Temporal Cloud or an @@ -50,6 +55,11 @@ services: TEMPORAL_ADDRESS: temporal:7233 ports: - '127.0.0.1:8233:8080' + deploy: + resources: + limits: + cpus: '0.25' + memory: 256M restart: unless-stopped backend: diff --git a/deploy/ai-studio/docker-compose.yml b/deploy/ai-studio/docker-compose.yml index 55c1e107b..7ff307a68 100644 --- a/deploy/ai-studio/docker-compose.yml +++ b/deploy/ai-studio/docker-compose.yml @@ -50,6 +50,11 @@ services: interval: 5s timeout: 3s retries: 12 + deploy: + resources: + limits: + cpus: '1.0' + memory: 512M restart: unless-stopped # applies migrations at boot; on failure exits and `restart` retries @@ -89,6 +94,11 @@ services: timeout: 5s retries: 6 start_period: 15s + deploy: + resources: + limits: + cpus: '0.5' + memory: 512M restart: unless-stopped # crash-loops until Temporal answers (no usable healthcheck); restart converges it @@ -112,6 +122,11 @@ services: # backend healthy = migrations applied backend: condition: service_healthy + deploy: + resources: + limits: + cpus: '1.0' + memory: 1G restart: unless-stopped web: @@ -127,6 +142,11 @@ services: - '${WEB_BIND:-0.0.0.0}:${WEB_PORT:-8080}:80' depends_on: - backend + deploy: + resources: + limits: + cpus: '0.25' + memory: 128M restart: unless-stopped volumes: