Demo full-stack to-do application showcasing a Vite/React frontend, FastAPI backend, and Terraform-based Azure deployment. Use this repo to experiment with modern tooling (ShadCN UI, SQLModel, structlog) and IaC workflows targeting Azure Container Apps + Static Web Apps.
backend/ # FastAPI app, SQLModel models, pytest suite
frontend/ # Vite + React + ShadCN UI client
infra/ # Terraform root + modules for Azure resources
docs/ # Architecture notes, decision logs
scripts/ # Helper scripts (build/push images, seed data, etc.)
- Node.js 20+, pnpm
- Python 3.11, uv
- Terraform 1.6+
- Azure CLI (logged in to your subscription)
- Copy environment templates:
cp .env.example backend/.env cp .env.example frontend/.env
- Install dependencies:
(cd backend && uv sync --extra dev) (cd frontend && pnpm install)
- Local dev servers:
- Backend:
cd backend && uv run uvicorn app.main:app --reload - Frontend:
cd frontend && pnpm dev
- Backend:
- Run tests:
- Backend:
cd backend && uv run pytest - Frontend:
cd frontend && pnpm test
- Backend:
- Use the Docker workflow below for an integrated local dev loop.
- Copy the Docker env template:
cp backend/.env.docker.example backend/.env.docker
- Start the stack (database, backend API, frontend dev server):
Override defaults by appending extra docker-compose flags, e.g.
./scripts/dev.sh
./scripts/dev.sh --build. - Tear everything down (including data volume):
docker compose down -v
- Authenticate – run
az loginandaz account set --subscription <id>. - Bootstrap remote state (first time only):
RESOURCE_GROUP=rg-azuretodo-dev \ LOCATION=japaneast \ STORAGE_ACCOUNT=azuretodotfstate \ CONTAINER=tfstate \ ./scripts/bootstrap_tf_state.sh
- Configure Terraform – copy
infra/terraform/terraform.tfvars.exampletoterraform.tfvars, filling in subscription/tenant IDs,container_image, Postgres admin credentials, and ACR metadata. - Build & push the backend image (ships linux/amd64 for Container Apps):
Override
./scripts/build_and_push_backend.sh --acr-name <acr-name>
--image-name/--tagif you need custom values; the script pushes both the git SHA andlatesttags. - Provision infrastructure:
Terraform outputs include the API FQDN, frontend endpoint, Key Vault, and Postgres hostnames.
(cd infra/terraform && terraform init -backend-config=... && terraform plan && terraform apply)
- Upload the frontend (optionally overriding the API base URL with the Container App FQDN):
Pass
./scripts/deploy_frontend.sh \ --storage-account <storage-account> \ --api-url https://<container-app-fqdn>
--no-buildif you already havefrontend/dist;--resource-group/--subscriptionforward to Azure CLI calls. - Run database migrations (once per environment):
Grab the credentials from your Key Vault or Terraform outputs; append
cd backend DATABASE_URL="postgresql+psycopg://<admin_login>:<admin_password>@<postgres_fqdn>:5432/<db_name>" \ uv run alembic upgrade head
?sslmode=requireif your policy enforces TLS.
Subsequent releases typically run steps 4–6. Update container_image in terraform.tfvars whenever you push a new backend tag.
GitHub Actions workflow .github/workflows/ci.yml executes on every push/PR to main:
- Backend job installs dependencies via
uvand runsuv run pytest. - Frontend job installs pnpm dependencies and runs
pnpm test.
On successful pushes to main, the deploy job runs and:
- Builds a linux/amd64 backend image with Docker Buildx and pushes it to the configured ACR using the commit SHA for tagging.
- Runs
terraform apply(with remote state) so the Container App pulls the new image and updated environment settings. - Executes Alembic migrations against the managed PostgreSQL instance.
- Builds the Vite frontend and uploads the assets to the
$webcontainer of the storage account viaaz storage blob upload-batch --auth-mode login.
| Secret | Description |
|---|---|
AZURE_CREDENTIALS |
Service principal JSON (az ad sp create-for-rbac --sdk-auth) including clientId, clientSecret, tenantId, and subscriptionId. |
AZURE_SUBSCRIPTION_ID |
Azure subscription GUID (must match subscriptionId in the credentials JSON). |
AZURE_TENANT_ID |
Azure tenant GUID corresponding to the service principal. |
ACR_NAME / ACR_LOGIN_SERVER |
Existing Azure Container Registry name (e.g. azuretodoregistry) and login server (azuretodoregistry.azurecr.io). |
ACR_RESOURCE_GROUP |
Resource group that houses the ACR instance. |
TF_VAR_PREFIX |
Resource name prefix used by Terraform (e.g. azuretodo). |
TF_STATE_RESOURCE_GROUP |
Resource group hosting the Terraform remote state storage account. |
TF_STATE_STORAGE_ACCOUNT |
Storage account name for Terraform state. |
TF_STATE_CONTAINER |
Blob container name for Terraform state (e.g. tfstate). |
TF_STATE_KEY |
Blob key used for this workspace’s state file (e.g. azure-todo-dev.tfstate). |
POSTGRES_ADMIN_LOGIN / POSTGRES_ADMIN_PASSWORD |
Flexible Server admin username and password injected into Terraform and migrations. |
POSTGRES_DB_NAME |
Logical database name (defaults to todoapp). |
STORAGE_ACCOUNT |
Storage account used for the Static Website hosting the frontend. |
Optional secrets:
TF_VAR_custom_domainif you configure a custom hostname for the static site.TF_VAR_ENVIRONMENTto override the deployment environment (defaults todev).TF_VAR_LOCATIONto override the Azure region (defaults tojapaneast).
The deployment job expects Terraform backend access and Azure CLI operations to succeed using the supplied service principal. Ensure the account has Contributor (or more restrictive, appropriately-scoped) rights on the resource group(s) hosting the stack.
Note: The workflow automatically imports the existing resource group into Terraform state if it is missing. The name is derived from the lowercased, alphanumeric prefix (e.g.
azuretodo) and the environment (devby default), producingrg-azuretodo-dev. If you change those values, keep the naming convention consistent.
Fix failing jobs locally using the same commands before pushing changes.
PLANS.md– living ExecPlan describing the full build.AGENTS.md– orientation + working agreements.docs/architecture.md– architecture overview + deployment workflow diagram.infra/terraform/README.md– Infrastructure-as-code usage + deployment notes.scripts/bootstrap_tf_state.sh– helper to create Terraform remote state RG/storage/container.
Phase 4 Terraform scaffolding is ready; next up is wiring pipelines + deployments per PLANS.md.