Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions .github/workflows/deploy-ai-studio.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
set -e
cd /app/ai-studio
if [ -f .env ] && grep -Eq '^OPENROUTER_API_KEY=.+' .env; then
echo "OPENROUTER_API_KEY is still set in the VM's .env. It was renamed to AI_API_KEY and is no longer read; rename it and set AI_BASE_URL and AI_MODEL too (values in deploy/ai-studio/.env.example), then deploy again."
exit 1
fi
echo "$COMPOSE_B64" | base64 -d > 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
Expand Down
15 changes: 15 additions & 0 deletions deploy/ai-studio/docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,6 +38,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.
Expand All @@ -45,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:
Expand Down
20 changes: 20 additions & 0 deletions deploy/ai-studio/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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:
Expand Down
Loading