Files
The-transit-Project/.github/agents/azure-deploy/prepare/references/deploy-strategy.md
T
Sven laptop 635a130f0b
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
──────
### Summary of Changes
  1. Documentation Consolidation:
      • Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
      DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
      SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
      • Updated document links in README.md.
  2. AI Agent Project Map:
      • Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
      schemas, environment variables, and developer workflows.
      • Added PROJECT_MAP.md to .gitignore.
  3. Monitoring UI & Infrastructure Integration:
      • Added monitoring_ui service to docker-compose.yml (port 5175).
      • Configured Gitea workflow docker-build.yml for monitoring_ui builds.
      • Added CORS middleware headers in index.js.
      • Added nginx.conf copy step in Dockerfile.

  ──────
  ### Recommended Commit Message

    docs: consolidate project documentation into monitoring_ui and add agent project map

    - Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
    - Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
    - Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
    - Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
    - Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
    - Configure CORS handling in employee_api for monitoring dashboard access
    ──────
  ### Alternative Short Commit Message

    refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
2026-09-20 23:33:55 +02:00

7.4 KiB

Important: This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.

Deploy Strategy

Determine how application code will be deployed to Azure based on prereq scan results. The prepare phase writes deployStrategy to prepare-plan.json; scaffold encodes it in Bicep; deploy executes it.

Deployment Patterns

Three patterns — select based on prereq-output.json.components[].buildRequirements:

⛔ Dockerfile ≠ Container Apps. A Dockerfile that only serves static files (nginx, httpd, COPY . /usr/share/nginx/html) is NOT a backend app. Route static-only Dockerfiles as static sites per service-mapping.md § Static Dockerfile sites, not Pattern C.

Pattern When deployStrategy needed?
A: Oryx auto-build No native modules, no Dockerfile Yes — startup command + app settings go in Bicep at scaffold time
B: Startup-install Native modules detected (hasNativeModules: true) Yes — startup command + fallback install + app settings in Bicep
C: Container-only Has Dockerfile that runs a server process (Express, Flask, uvicorn, etc.) No — route to Container Apps (Dockerfile IS the deploy strategy)

Additional routing:

Condition Action
Jib build plugin (Java + com.google.cloud.tools.jib) Container-only via Jib push to ACR — no Dockerfile needed

Pattern A: Oryx Auto-Build (Default)

Languages: Node.js, Python, .NET, Go, Java, PHP, Ruby

Oryx detects the stack from project manifests, installs dependencies, and builds automatically during zip deploy.

Still write deployStrategy to prepare-plan.json — even though Oryx auto-detects, the startup command and app settings MUST be in Bicep at scaffold time (not generated at deploy time). This eliminates imperative CLI commands during deploy.

"deployStrategy": {
  "codeDeployPattern": "oryx-auto",
  "requiredAppSettings": {
    "SCM_DO_BUILD_DURING_DEPLOYMENT": "true",
    "ENABLE_ORYX_BUILD": "true",
    "ORYX_DISABLE_COMPRESSION": "true"
  },
  "reason": "Standard Oryx build — no native modules, no Dockerfile. Compression disabled to avoid startup extraction delays. No custom appCommandLine — Oryx launcher handles decompression + start."
}

Read prereq-output.json.entryPoint for the app's start file — do NOT re-read manifests. Build the startup command from package.json start script (Node.js) or framework convention (Python gunicorn, .NET/Go/Java Oryx-native).

⛔ Do NOT set a custom appCommandLine for Pattern A. Let Oryx use package.json start script or framework defaults natively. A custom appCommandLine (cd /home/site/wwwroot && node {entryPoint}) replaces the Oryx launcher entirely — the launcher handles node_modules.tar.gz decompression, and bypassing it causes MODULE_NOT_FOUND crashes. Only set appCommandLine when initCommands[] has required: true entries (migrations).

⛔ TypeScript projects: Verify typescript + @types/* are in dependencies (not devDependencies) — Oryx production mode skips devDeps, causing tsc build failures.

⛔ When initCommands[] has required: true entries: Set startupCommand to prepend migrations: "cd /home/site/wwwroot && {initCommand} && {framework-default-start}". Migrations are idempotent — safe on every cold start. Otherwise, omit startupCommand entirely (let Oryx handle it).

Scaffold encodes startupCommand → Bicep appCommandLine, and requiredAppSettings → Bicep siteConfig.appSettings. Deploy only does: wait → zip → health check.


Pattern B: Startup-Install (Native Modules)

When native modules are detected, Oryx may fail to compile them. The startup-install pattern provides a two-layer safety net.

Two-Layer Strategy

  1. Primary — Oryx zip build: SCM_DO_BUILD_DURING_DEPLOYMENT=true + ENABLE_ORYX_BUILD=true tells Oryx to run dependency installation during the Kudu-side zip deploy. The Kudu build environment on App Service Linux has gcc, make, and build tools available, so native compilation CAN succeed here.

  2. Fallback — startup-install command: appCommandLine runs dependency installation on first container boot IF the dependency directory doesn't exist. The existence guard ensures it only runs when needed — subsequent restarts skip it because /home is persistent storage.

Both layers are set in Bicep at scaffold time. The startup command is insurance — not the primary mechanism.

Why two layers? az webapp deploy --type zip uses the OneDeploy API, which may not trigger Oryx even with SCM_DO_BUILD_DURING_DEPLOYMENT=true. The startup command catches this case. If Oryx DID build successfully, the guard skips the redundant install.

Deploy Strategy Schema

Write to prepare-plan.json.deployStrategy:

"deployStrategy": {
  "codeDeployPattern": "startup-install",
  "startupCommand": "cd /home/site/wwwroot && if [ ! -d node_modules ]; then npm install --production; fi && node index.js",
  "requiredAppSettings": {
    "WEBSITES_CONTAINER_START_TIME_LIMIT": "1800",
    "SCM_DO_BUILD_DURING_DEPLOYMENT": "true",
    "ENABLE_ORYX_BUILD": "true",
    "ORYX_DISABLE_COMPRESSION": "true"
  },
  "reason": "Native module (better-sqlite3 via node-gyp) requires server-side npm install."
}

Replace startupCommand and reason with language-specific values from the entry point table below.

Entry Point & Startup Commands

Same as Pattern A, but Node.js adds a dependency guard: if [ ! -d node_modules ]; then npm install --production; fi before the start command.

⛔ Inline commands only. Never generate a .sh startup script file — CRLF causes bash exit code 2. ⛔ Python: do NOT use venv in startup commands.

SKU Implications

The compute floor is B1 (Basic, ~$13/mo) — F1/D1/Free are never selected (managed identity is mandatory and the free-tier MI sidecar OOMs). When native modules, TypeScript build, large deps, or a WSGI/ASGI server push resource needs higher, size up from B1 (B2/S1) and surface the reason at the approval gate: "⚠️ {reason}. {sku} required." Never size below B1.

Container Timeout

WEBSITES_CONTAINER_START_TIME_LIMIT controls how long Azure waits for the container to start responding.

Value Use case
230 (default) Standard apps, no native compilation
1800 (max) Startup-install — native compilation takes 2-5 min. Python with scipy/scikit-learn can take longer

Always set to 1800 when codeDeployPattern == "startup-install".


Pattern C: Container-Only

When the component has a Dockerfile with backend logic, route to Container Apps. The Dockerfile IS the deploy strategy — no deployStrategy needed in prepare-plan.json.

The deploy phase handles: ACR build → image push → Bicep redeploy with real image. See code-deployment-container-apps.md.

For Java apps using Jib (build.gradle + com.google.cloud.tools.jib), the build produces a container image without a Dockerfile — push to ACR via jib task.