SmartProperty DevOps on Azure

About 7 minutes from merge to verified production, with automatic rollback: the CI/CD, security scanning and monitoring I built for SmartProperty, a team-built rental platform on Azure.

  • About 7 minutes from merge to verified production; a failed check rolls the API back automatically
  • No cloud secret stored in GitHub: deployments sign in to Azure with OIDC
  • Hosting cut from about $20.70 to about $8.40 a month, so the student credit lasts until it expires
DevOps & InfrastructureCompleted
GitHub Actions run with four green jobs: test, build and push the API image, deploy the API, deploy the frontend.

Overview

SmartProperty is a property rental platform built as a team project at ESPRIT: a NestJS API, a React/Vite frontend and a MongoDB Atlas database. The team built the application; the DevOps phase was my work.

That meant a pipeline that tests and ships every merged change, monitoring that tells me when something breaks, security checks on every push, and hosting that fits within a $100 student credit.

  • API: Docker image on Azure Container Apps (Italy North, "express" environment)
  • Frontend: static build on an Azure Storage static website, served through Cloudflare (DNS, TLS, proxy) on the custom domain
  • Files: Cloudflare R2 (S3-compatible)
  • Database: MongoDB Atlas
  • Infrastructure as code: Bicep; CI/CD: GitHub Actions

How a change reaches production

Every push and pull request runs CI on GitHub Actions:

  • Type checks, unit tests with coverage, and production builds of the API and the frontend
  • SonarQube Cloud analysis (the quality gate is advisory for now)
  • Bicep infrastructure templates compiled and linted

A merge to main then deploys both halves, about 7 minutes from merge to verified production:

  • Keyless sign-in: GitHub authenticates to Azure with OIDC, so no cloud secret is stored
  • The student tenant forbids app registrations, so the federated credential sits on a user-assigned managed identity that only trusts jobs in the repository's "production" environment
  • That identity holds three narrowly scoped roles: the container app, its environment's join permission, and the static site's $web container
  • The API image is deployed by digest
  • The API reports the git commit it was built from; the pipeline waits for that exact commit, then 10 healthy checks in a row, then a real database read
  • If any check fails, the pipeline rolls back to the previous image automatically
  • The frontend publishes hashed bundles first (cached for a year), other files next and index.html last (never cached), then checks that the live site serves the new bundle

Monitoring

Monitoring is defined as code in Bicep, on Azure Monitor:

  • An availability test calls the API from outside Azure every 15 minutes, including a TLS certificate expiry check
  • Alerts are emailed to me for: API down, no running replica, a container restart, memory over 80%, and 5 or more logged errors within 15 minutes
  • I set the error threshold from a week of real logs: normal running logs 0–2 errors a day
  • A Log Analytics workbook shows availability, container metrics, errors grouped by message, and a deploy history built from each container start and the commit it runs

Azure blocks test notifications on student subscriptions, so I fired a deliberate alert to prove the email path end to end. Calibrating the alerts also surfaced a real bug: production email had never been configured.

Security

Security checks run on every push, plus weekly:

  • gitleaks secret scanning, CodeQL, dependency scanning, hadolint for Dockerfiles, a Trivy image scan and an SBOM
  • Hardened API image: multi-stage, runs as non-root, base image pinned by digest
  • Removing npm and yarn from the runtime layer cleared one critical and about a dozen high Trivy findings
  • The secret scanner flagged public frontend keys; I handled them with explicit allow markers and a fingerprint ignore file, so the scanner stays strict everywhere else

In the application itself:

  • ID and income verification documents moved to a separate private bucket, readable only through signed links that expire after 10 minutes
  • Two unsafe upload endpoints removed
  • Leaked credentials are being rotated: the storage keys, the Google OAuth secret and the database password are done
  • The database now uses a least-privilege user limited to one database

Cost

I measured spending with Cost Management. Reserved CPU and memory are billed even when idle, so the first setup would have used the $100 student credit in under 5 months.

  • Resized the API container to the smallest size
  • Trimmed monitoring
  • Kept one always-warm replica, so visitors never hit a cold start

The bill went from about $20.70 a month to about $8.40, enough to last until the credit expires.

Challenges and decisions

  • Infrastructure drifts quietly. The deploy job was set to re-apply a Bicep template that no longer matched production, because Atlas and R2 had replaced the original setup. The first automated deploy would have overwritten live settings, so I removed that step before switching CD on.
  • Platform limits shaped the design: region policies, no app registrations, and an environment without blue/green deployments.
  • Without blue/green, a deploy can briefly drop requests. The post-deploy checks allow for that, but not for a build that keeps failing.

Key Features

Keyless deploys with OIDC
API deployed by image digest
Post-deploy commit, health and database checks
Automatic rollback
Cache-safe frontend publishing
Security scans on every push and weekly
Alerts and dashboard defined in Bicep
Hosting cut from ~$20.70 to ~$8.40 a month

Technologies Used

GitHub ActionsDockerAzure Container AppsAzure StorageAzure MonitorApplication InsightsLog Analytics (KQL)BicepOIDC / Managed IdentityCloudflareCloudflare R2MongoDB AtlasSonarQube CloudgitleaksCodeQLTrivyhadolintNestJSReact

Project Screenshots