• English
  • Operations

    Local development

    Prerequisites

    • .NET 10 SDK
    • Docker
    • Node.js (for CDK synth tests only)

    Start all services

    docker compose up --build

    All services start with their own container. An nginx edge proxy on :8080 routes:

    Path prefixService
    /v1/sync/*, /v1/restore/*, /v1/stores/*/mediaIngestion
    /v1/ops/*Admin
    /v1/book/*, /v1/booking/*Booking
    /v1/sms/*Messaging
    /v1/push/*Push
    /v1/logs/*, /v1/commands/*, /v1/admin/*Observability
    /internal/*Blocked at edge (403)
    Everything else (catch-all)Accounts

    Developer tools

    pgAdmin (optional, browser-based DB viewer):

    docker compose --profile tools up -d pgadmin
    # Open http://localhost:8081, login: admin@local.dev / admin
    # Server "SalonCloud (local)" is pre-configured; enter password "postgres"

    Seed demo booking store:

    psql "Host=localhost;Port=5432;Database=salon;Username=postgres;Password=postgres" \
      -f tools/dev-seed-booking.sql
    
    # Then test:
    curl -s localhost:8080/v1/book/demo-salon/profile
    curl -s "localhost:8080/v1/book/demo-salon/availability?serviceId=7&date=$(date -v+Mon +%Y-%m-%d)"

    Email/SMS in development

    By default, Messaging uses logging transports (no real sends). To use real providers:

    • Gmail: Create .env with EMAIL_PROVIDER=smtp, GMAIL_FROM, GMAIL_APP_PASSWORD
    • Twilio: Add SMS_PROVIDER=twilio, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_MESSAGING_SERVICE_SID

    See docker-compose.yml for all environment variable placeholders.

    Container images

    All services use .NET 10 chiseled runtime images (mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled) — minimal, shell-less, non-root (USER $APP_UID).

    DockerfileServiceSpecial notes
    DockerfileIngestionCopies SalonCloud.Infrastructure, DataHub.Infrastructure, Media, Shared
    Dockerfile.accountsAccounts
    Dockerfile.bookingBookingCopies IANA timezone data from SDK (chiseled lacks it; Booking does TimeZoneInfo.FindSystemTimeZoneById)
    Dockerfile.adminAdmin
    Dockerfile.engineEngine
    Dockerfile.pushPush
    Dockerfile.messagingMessaging
    Dockerfile.observabilityObservability

    All follow the same multi-stage pattern: restore → publish → chiseled runtime, EXPOSE 8080.

    AWS infrastructure (CDK)

    Infrastructure is defined in infra/SalonCloud.Infra/ as C# CDK stacks.

    Stack architecture

    Program.Main()
      ├── FoundationStack     (VPC, RDS, ECS Cluster, ALB, Cloud Map, InternalBridgeSecret)
      ├── MediaStack          (S3 bucket + CloudFront)
      ├── IngestionStack
      ├── AccountsStack
      ├── ObservabilityStack
      ├── AdminStack
      ├── BookingStack
      ├── PushStack
      ├── MessagingStack
      └── EngineStack

    Foundation stack details

    ResourceDetails
    VPC2 AZs, 3-tier subnets (public/private-with-egress/isolated), 1 NAT gateway
    RDSPostgreSQL 16, db.t4g.small (PROD) / db.t4g.micro (UAT)
    ECS ClusterFargate (PROD) / EC2 t3.micro (UAT)
    ALBSingle internet-facing ALB, shared HTTPS listener
    InternalBridgeSecretShared secret for inter-service X-Internal-Secret header
    Cloud MapService discovery namespace for Service Connect

    Per-service stack pattern

    Each service stack uses FargateWebService.Create() — a shared helper that provisions:

    • ECS task definition (EC2/HOST networking in UAT, Fargate/AWSVPC in PROD)
    • Target group + ALB listener rule (path-based routing)
    • Auto-scaling (CPU tracking)
    • Secrets Manager entries for connection strings and internal secrets

    Environment differences (DeployEnv)

    KnobUATPROD
    Launch typeEC2 (t3.micro)Fargate
    RDS instancedb.t4g.microdb.t4g.small
    Multi-AZNoYes
    Deletion protectionNoYes
    Min task count12

    Service discovery

    • Fargate (PROD): Services discover each other via Service Connect DNS (e.g., http://accounts:8080)
    • EC2 (UAT): Services use HOST networking with fixed port mapping, discover via http://localhost:<port> — avoids Service Connect proxy overhead

    CI/CD

    CI (ci.yml)

    • Trigger: push or PR to main
    • Job: build-test on ubuntu-latest
      1. Checkout
      2. Setup .NET 10
      3. dotnet restore
      4. dotnet build --configuration Release --no-restore
      5. dotnet test --configuration Release --no-build (all tests including integration)

    Deploy UAT (deploy-uat.yml)

    • Triggers:
      • Push a uat/** tag (e.g., uat/008)
      • Manual workflow_dispatch with service selection
    • Jobs:
      1. detect: Resolve/create tag, detect changed services via git diff, build JSON matrix
      2. Build & push: Build Docker images → push to ECR
      3. CDK deploy: Deploy Foundation + changed service stacks

    Wake UAT (wake-uat.yml)

    • Trigger: manual workflow_dispatch
    • Purpose: UAT auto-scales to 0 during off-hours (Tue–Sun 03:00 UTC). This workflow restores MinCapacity=1 and desired count to 1 for all services

    Configuration

    Connection strings

    All services read SALONCLOUD_DB environment variable for PostgreSQL connection. In AWS, connection strings are composed in Secrets Manager; password is sourced from the RDS secret.

    Internal bridge secret

    Inter-service HTTP calls use X-Internal-Secret header. The shared secret is in InternalBridgeSecret env var (local: dev-secret; cloud: Secrets Manager from FoundationStack).

    Service-specific URLs

    Each service is configured with URLs for its dependencies:

    ServiceConfig keys
    AccountsIngestionInternalUrl, ObservabilityInternalUrl, BookingInternalUrl, PushInternalUrl, MessagingInternalUrl
    BookingPushInternalUrl, MessagingInternalUrl
    AdminAccountsInternalUrl, BookingInternalUrl, EngineInternalUrl, MessagingInternalUrl, ObservabilityInternalUrl
    EngineAccountsInternalUrl
    ObservabilityPushInternalUrl

    Email/SMS providers

    ProviderConfig
    Gmail SMTPMessaging__Email__Provider=smtp + Smtp__Host/Port/Username/Password
    Twilio SMSSms__Provider=twilio + Twilio__AccountSid/AuthToken/MessagingServiceSid
    Dev (default)Empty provider → logging transport

    Seed scripts

    ScriptPurpose
    tools/dev-seed-booking.sqlSeeds a demo bookable store (slug demo-salon) into ingestion.ingest_rows + booking tables
    tools/dev-seed-ssos.sqlSeeds SSOS test data
    tools/dev-seed-ssos-p1c.sqlSeeds SSOS Phase 1C test data

    Run manually with psql — never against production.

    UAT deploy helper

    tools/uat-deploy/build-push.sh — convenience script for building and pushing images to ECR without full CDK deploy. See tools/uat-deploy/README.md.

    More on testing in Testing.