What to change first
A checklist for a new project built from the AppointMe .NET template. Names, branding, committed dev secrets, demo mode, the Keycloak realm, unused modules, licenses, CI and Azure defaults.
- You'll end with
- A project that carries your name
- When
- Before your first deploy
- Touches
- Config, realm, SPA,
infra/,.github/ - Before you start
- A running stack from the Quick start
This page assumes you generated the project with dotnet new appointme -n Contoso.Booking. If you cloned the repo instead, nothing has been renamed yet: start with Rename your project. Paths below use the upstream AppointMe names; in your project they carry yours.
Names: what -n changed and what it didn’t
Section titled “Names: what -n changed and what it didn’t”dotnet new -n renames every brand token it can match without touching the domain word “appointment”. Three build properties still name BravoDev.
| Item | After -n Contoso.Booking |
|---|---|
| Namespaces, projects, folders, the solution | Contoso.Booking.Api, src/Contoso.Booking.Aspire, Contoso.Booking.sln |
| The database | Contoso.Booking |
| The Keycloak realm and its file | contoso.booking, in contoso.booking-realm.json |
| The Compose project, containers and Aspire resources | contoso-booking, contoso-booking-sql, contoso-booking-api |
| The demo account | demo@contoso.booking.dev, password ContosoBooking1 |
| Domain words | Unchanged: Appointment, /appointments, appointments.statistics:view |
| What | Where | Why | How |
|---|---|---|---|
Company, Authors, RepositoryUrl |
Directory.Build.props |
They’re still BravoDev and github.com/bravodev-hub/..., and every assembly you build embeds them |
Set your company, authors and repository URL |
| The product name in the browser tab | src/AppointMe.Frontend/index.html, <title> |
-n put your dotted name there, for example Contoso.Booking |
Write the name users should see |
<Company>BravoDev</Company><Authors>BravoDev</Authors><RepositoryUrl>https://github.com/bravodev-hub/appointme.git</RepositoryUrl>templates/README.md in the upstream repo documents the rename rules token by token.
Branding in the frontend
Section titled “Branding in the frontend”The SPA’s logo, wordmark and colors live in a handful of files. The wordmark is split across a <span>, so -n didn’t touch it.
| What | Where | Why | How |
|---|---|---|---|
| Wordmark | Appoint<span className='font-semibold'>Me</span> in src/app/layouts/sidebar/app-sidebar.tsx, src/app/layouts/footer/footer.tsx, src/app/auth/signup/signup.tsx, src/app/onboarding/onboarding.tsx and src/app/invitations/invitations.tsx |
It still reads “AppointMe” on every screen | Replace it in all five files, or move it into one component first |
| Sidebar logo | src/app/layouts/sidebar/app-logo.tsx |
AppLogo is an inline SVG of the AppointMe mark |
Swap the path, or render your own SVG |
| Favicon | src/assets/logo.svg, linked from index.html |
The same mark, as a file | Replace the SVG |
| Colors | src/index.css, the :root and .dark blocks |
The palette is neutral grays (--primary: oklch(0.205 0 0)) |
Change the tokens. The @theme inline block maps them to Tailwind colors. |
| Footer version | src/app/layouts/footer/version.ts |
Shows VITE_APP_VERSION, which the Docker build sets, or dev locally |
Nothing to change, unless you version differently |
Paths in this table are relative to src/AppointMe.Frontend/. Frontend overview covers the rest of the SPA.
Committed dev secrets and demo mode
Section titled “Committed dev secrets and demo mode”Every local credential is committed on purpose and predictable in every project made from the template. Never reuse one outside your machine, and decide where demo mode stays on.
| Secret | Where | Value |
|---|---|---|
SQL Server sa password |
src/AppointMe.Aspire/Program.cs (sqlPassword), compose.yaml (MSSQL_SA_PASSWORD), appsettings.Development.json (connection string) |
Password1 |
| Keycloak admin | Program.cs (username, password), compose.yaml (KC_BOOTSTRAP_ADMIN_*) |
admin / admin |
appointme-api client secret |
appointme-realm.json, and KeycloakAdmin:ClientSecret in appsettings.Development.json |
A fixed 32-character string |
appointme-frontend client secret |
Authentication:Keycloak:FrontendClientSecret in appsettings.Development.json |
A fixed 32-character string |
| Demo user | appointme-realm.json, and Demo:User in appsettings.Development.json |
demo@appointme.dev / AppointMe1, renamed by -n as above |
These only protect containers on your machine. Deployed environments read their secrets from Azure Key Vault, so never copy one of these values into a deployed environment’s settings. If you rotate them, change every place in the row together, and see the gitleaks note under CI.
| What | Where | Why | How |
|---|---|---|---|
Demo:Enabled |
true in appsettings.Development.json, false in appsettings.json, true in appsettings.Devtest.example.json |
It turns on the demo login, demo data for every new company, and two daily top-up jobs at 03:00 UTC | Keep it for local work. Set it to false in any environment real customers use. |
| Demo seeding | src/AppointMe.Api/Demo/DemoSeedingSaga.cs |
Every company registered while demo mode is on gets 150 fake customers and about 450 appointments | Turn demo mode off, or delete the saga and the SeedDemo* and TopUpDemo* slices |
| Super admins | Authentication:SuperAdmins |
The demo user is a super admin in Development, which opens /admin/jobs |
List your own operator emails per environment |
| Dashboard revenue | src/AppointMe.Frontend/src/app/dashboard/dummy-revenue.ts |
There’s no payments domain, so revenue is fake data derived from appointment counts | Replace it with a real endpoint, or hide the revenue widgets |
The demo gate is the first thing the saga checks:
public static DemoSeedingSaga? Start(CompanyRegistered @event, IConfiguration configuration){ if (!configuration.GetValue<bool>("Demo:Enabled")) { return null; }
return new DemoSeedingSaga { Id = @event.CompanyId, CustomersExpected = CustomerCount, };}The Keycloak realm
Section titled “The Keycloak realm”Keycloak runs only on your machine, from one committed realm file. Two of its settings point at BravoDev or the template’s ports.
| What | Where | Why | How |
|---|---|---|---|
| Google sign-in | identityProviders in appointme-realm.json |
It carries BravoDev’s Google OAuth client ID and an empty secret, and “Continue with Google” on the sign-up form uses it | Put in your own Google client ID and secret, or remove the provider and the button in src/components/signup/signup-form.tsx |
| Redirect URIs | The appointme-frontend and appointme-api clients |
They allow only https://localhost:5173 and https://localhost:7233 |
Update them if you move the dev ports |
| Email sender | smtpServer |
Mail goes from noreply@appointme.local to the Mailpit host on port 1025 |
Change the display name if you like. Keep the host for local work. |
| Login page look | The realm sets no loginTheme |
Users see Keycloak’s default theme locally | Add a theme only if local branding matters to you |
Keycloak imports the realm only when it doesn’t exist yet, and its container keeps a data volume. To apply an edit, remove the Keycloak container and its volume, then start the stack again; this also deletes the local users you signed up. Deployed environments use Entra External ID instead: see Entra External ID.
Modules, licenses and notices
Section titled “Modules, licenses and notices”Delete the modules you won’t use while the project is young, and keep BravoDev’s copyright line in LICENSE.
| What | Where | Why | How |
|---|---|---|---|
| Unused modules | src/<Module>/, the Add<Module>Module() call in Program.cs, the API’s project references, the SPA’s pages |
Each one adds tables, endpoints and permissions | Follow Remove a module |
LICENSE |
Repo root | It reads Copyright (c) 2026 BravoDev. The MIT license requires every copy to keep that notice. |
Keep it. Add your own copyright line next to it if you want one. |
THIRD-PARTY-NOTICES.md |
Repo root | A curated list of dependency licenses. Hangfire is LGPL-3.0 (or commercial). | Using the unmodified NuGet package is fine. Modifying Hangfire’s own source triggers the LGPL obligations. |
| Full license report | The commands at the end of THIRD-PARTY-NOTICES.md |
The file isn’t an exhaustive transitive list | Generate one with dotnet-project-licenses and license-checker-rseidelsohn before you ship a binary or image |
CI and infra defaults
Section titled “CI and infra defaults”The workflows run as soon as you push to GitHub. The Bicep is sized for a demo environment, not production.
| What | Where | Why | How |
|---|---|---|---|
| Deploy job | .github/workflows/devtest.yml |
Build and test run on every push to main, every v* tag and every PR. The deploy job runs on v* tags (or a manual run) and needs six secrets (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP, ACR_NAME, APP_SERVICE_NAME). |
Set them as in GitHub Actions CI/CD, or delete the job until you need it |
| Secret scanning | .github/workflows/secret-scan.yml, .gitleaks.toml |
gitleaks scans the full history on every PR and push to main. The allowlist matches the committed dev values by value. |
If you commit rotated dev values, add them to the allowlist. Better, keep real values out of Git. |
| Code scanning | .github/workflows/codeql.yml |
CodeQL for C# and TypeScript on push, PR and every Monday | Keep it. The workflow notes it’s free for public repos. |
| Dependabot | .github/dependabot.yml |
Weekly NuGet, npm and Actions PRs. It skips Vite+ and container images. | Bump Vite+ and the images in Program.cs and compose.yaml by hand |
| Template workflow | .github/workflows/template.yml |
Only in a clone: it publishes BravoDev’s package on v* tags. Generated projects don’t get it. |
Delete it in a clone |
| Plan and database size | infra/modules/app-service-plan.bicep (F1), infra/modules/sql.bicep (Basic) |
F1 unloads the app after about 20 idle minutes and has a daily CPU quota | Move to B1 or higher before real traffic |
| Environment and admin login | infra/main.bicepparam |
environmentName = 'devtest', SQL admin appointme_admin |
Set yours. The password comes from SQL_ADMIN_PASSWORD. |
| Cloudflare Worker route | infra/cloudflare-worker/wrangler.jsonc |
Its routes and zone_name point at a domain derived from your project name |
Set your domain, or delete the folder if you don’t front the app with Cloudflare |
infra/README.md ends with a prod-hardening checklist (private endpoints, WAF, Key Vault purge protection and more). Production checklist walks through it.
Verify it works
Section titled “Verify it works”After the changes, the BravoDev name should survive only where it must, and the build, tests and frontend check should still pass.
git grep -n -i "bravodev"git grep -n "Appoint<span"dotnet build AppointMe.slndotnet test AppointMe.slncd src/AppointMe.Frontend && npm ci && npm run checkOnce your generated project is in Git, bravodev should match only LICENSE and the generated README.md, and the wordmark search should find nothing.
| Symptom | Cause | Fix |
|---|---|---|
| Login fails after you rotated a Keycloak client secret | The realm and appsettings.Development.json disagree |
Use the same value in both, then recreate the Keycloak container and volume |
| Realm edits don’t show up | The realm already exists in Keycloak’s data volume, so the import is skipped | Remove the Keycloak container and its volume |
secret-scan fails after you rotated dev values |
gitleaks no longer recognizes them as allowlisted | Add the new values to .gitleaks.toml, or keep them out of Git |
| “Continue with Google” fails | The realm’s Google provider has no client secret | Configure your own Google client, or remove the provider and the button |
Why commit dev secrets at all?1 min · optional
Committed values let the whole stack start with no setup: Aspire, Compose, the API and the realm all agree on the same passwords from the first dotnet run. They protect nothing but containers on your own machine.
The risk is reuse. That’s why deployed environments read Key Vault, why the realm runs only locally, and why gitleaks allowlists these exact values by value rather than by file, so a real secret added to the same files still fails the scan.