# Quick start

> Run AppointMe locally. Install the .NET 10 template from NuGet or clone the repo, trust the dev certificate, then start the stack with .NET Aspire or Docker Compose.

Source page: https://appointme.dev/docs/start/quick-start/

* **You'll get:** The full stack running on `localhost`
* **Time:** Minutes, plus the first image pull
* **Touches:** Nothing: the committed defaults run as is
* **Before you start:** .NET 10 SDK, Docker, Node 24

## Prerequisites

**Install three tools and trust one certificate. The versions come from the repo’s own pins.**

| Requirement           | Version                                                                               | Pinned in                                              | Check with                               |
| --------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------- |
| .NET SDK              | `10.0.0` or later, no previews (`rollForward: latestMajor`)                           | `global.json`                                          | `dotnet --version`                       |
| Docker                | Running. Docker Desktop or another OCI-compatible runtime.                            | —                                                      | `docker info`                            |
| Node.js               | `24` recommended. The `engines` field accepts `^22.18.0 \|\| ^24.11.0 \|\| >=26.0.0`. | `src/AppointMe.Frontend/.node-version`, `package.json` | `node --version`                         |
| HTTPS dev certificate | Trusted, once per machine                                                             | Checked by `src/AppointMe.Aspire/DevCertificate.cs`    | `dotnet dev-certs https --check --trust` |

The first run pulls three container images and restores the NuGet and npm packages, so it takes a few minutes. Later runs reuse the containers.

## Run it

**Three paths: generate a renamed copy from NuGet and run Aspire, clone and run Aspire, or clone and run Docker Compose.**

1. **Get the code.** Generate a renamed copy from the NuGet template, or clone the repo as it is.

   * **dotnet new**

     ```bash
     dotnet new install BravoDev.AppointMe.Templates::1.2.1
     dotnet new appointme -n Contoso.Booking
     cd Contoso.Booking
     ```

   * **git clone**

     ```bash
     git clone https://github.com/bravodev-hub/appointme.git
     cd appointme
     ```

   `-n` must be a dotted .NET identifier such as `Contoso.Booking`, because it becomes namespaces, project names and folders. A name like `my-booking-app` generates a solution that doesn’t compile, and `dotnet new` still reports success. In a generated project, read `AppointMe` in the paths below as your name, for example `src/Contoso.Booking.Aspire`.

2. **Trust the HTTPS dev certificate.** The API, the frontend and Keycloak all serve HTTPS locally.

   ```bash
   dotnet dev-certs https --trust
   ```

   If it’s still reported as untrusted (for example, a certificate created by an older SDK), run `dotnet dev-certs https --clean` and trust it again.

3. **Start the stack.** Aspire runs everything. Compose runs only the backing services, so you start the API and the frontend yourself.

   * **Aspire**

     ```bash
     cd src/AppointMe.Aspire
     dotnet run
     ```

     Or open `AppointMe.sln` in Visual Studio or Rider, set `AppointMe.Aspire` as the startup project and press F5. The Aspire dashboard opens in your browser. Wait until every resource shows **Running**.

   * **Docker Compose**

     ```bash
     ./docker/keycloak/export-dev-cert.sh
     docker compose up -d
     docker compose ps
     dotnet run --project src/AppointMe.Api --launch-profile https
     ```

     The script trusts the dev certificate and exports it to `docker/keycloak/certs/` for Keycloak to serve. Without bash, run the export it wraps: `dotnet dev-certs https --format PEM --no-password -ep docker/keycloak/certs/keycloak.crt`. Start the API once `docker compose ps` shows SQL Server and Keycloak as `healthy`.

     Then, in a second terminal:

     ```bash
     cd src/AppointMe.Frontend
     npm ci
     npm run dev
     ```

4. **Open the app.** Go to `https://localhost:5173`, then [create an account](#create-your-first-account) or use the [demo login](#skip-sign-up-the-demo-login).

Gotcha

Under Compose, pass `--launch-profile https` to the API. Without it, `dotnet run` picks the first profile in `src/AppointMe.Api/Properties/launchSettings.json`, which is `http`. The API then listens only on `http://localhost:5179`, while the dev server proxies `/api` to `https://localhost:7233`.

The NuGet package is behind main

`BravoDev.AppointMe.Templates` is at v1.2.1 (27 Sep 2026). These docs describe `main`, which has unreleased changes: the Vite+ frontend toolchain, the OpenAPI document generated at build time, and the client at `src/api/client.ts`. Clone the repo to match the docs exactly, and see `CHANGELOG.md` under “Unreleased” for the full list.

## What happens on startup

**Aspire starts each resource after the ones it depends on. The API applies every module’s migrations as it starts.**

*Figure 1. The start order Aspire enforces with WaitFor.*

1. **AppHost** — checks the dev certificate
2. **Containers** — SQL Server, Keycloak, Mailpit
3. **API** — waits for all three, then migrates
4. **Frontend** — `npm install --no-save`, then Vite on `5173`

| Step                                                                                                            | Done by             | Where                                                            |
| --------------------------------------------------------------------------------------------------------------- | ------------------- | ---------------------------------------------------------------- |
| Checks the HTTPS dev certificate. Exits if it’s untrusted, or only warns on Linux.                              | AppHost             | `src/AppointMe.Aspire/DevCertificate.cs`                         |
| Starts SQL Server, Keycloak and Mailpit as persistent containers, which later runs reuse                        | AppHost             | `src/AppointMe.Aspire/Program.cs`                                |
| Imports the `appointme` realm: clients, roles, the demo user and SMTP to Mailpit                                | Keycloak            | `src/AppointMe.Aspire/appointme-realm.json`                      |
| Applies EF Core migrations for Identity, Organizations, CRM and Booking, each in its own schema                 | API                 | `src/AppointMe.Shared/Database/Migrations/DatabaseMigrations.cs` |
| Creates its message tables (`wolverine_*`, in `dbo` at this commit) and its job tables in the `hangfire` schema | Wolverine, Hangfire | `src/AppointMe.Api/Wolverine`, `src/AppointMe.Api/Hangfire`      |
| Installs npm packages without rewriting `package-lock.json`, then starts the dev server                         | AppHost             | `AddViteApp(...)` in `Program.cs`                                |

There’s no demo data yet at this point. It’s created per company: when a company is registered and `Demo:Enabled` is on, `DemoSeedingSaga` seeds 150 customers and about 450 appointments for it. Under Compose, the API steps run when you start the API.

## Where everything lives

**The app is on `https://localhost:5173`. Every credential below is a committed local default.**

| What                   | URL                                                     | Sign in with                                         |
| ---------------------- | ------------------------------------------------------- | ---------------------------------------------------- |
| The app                | `https://localhost:5173`                                | An account you sign up, or the demo login            |
| Demo login             | `https://localhost:5173/api/v1/login/demo`              | Nothing: it signs in `demo@appointme.dev`            |
| API                    | `https://localhost:7233` (also `http://localhost:5179`) | The app’s cookie, or a bearer token                  |
| OpenAPI document       | `https://localhost:7233/openapi/v1.json`                | Anonymous, Development only                          |
| Hangfire dashboard     | `https://localhost:5173/admin/jobs`                     | A super admin, which is `demo@appointme.dev` locally |
| Aspire dashboard       | `https://localhost:17082` (Aspire only)                 | The URL the AppHost prints in its console            |
| Keycloak admin console | `https://localhost:8082`                                | `admin` / `admin`                                    |
| Mailpit                | `http://localhost:8026`                                 | —                                                    |
| SQL Server             | `localhost,60740`                                       | `sa` / `Password1`                                   |

The demo user’s password is `AppointMe1`, if you’d rather sign in on the Keycloak page. [Local services & ports](/docs/reference/local-services/) lists every image, port and volume, and [What to change first](/docs/start/what-to-change-first/) covers why you must never reuse these values.

## Create your first account

**Sign-up runs through the app, and Keycloak’s verification email lands in Mailpit.**

1. **Sign up.** Open `https://localhost:5173/auth/signup` and enter your name and email. There’s no password field: the API creates the user in Keycloak with two required actions, `VERIFY_EMAIL` and `UPDATE_PASSWORD`, and asks Keycloak to send the verification email.

2. **Open the email in Mailpit.** Go to `http://localhost:8026`. The message comes from `noreply@appointme.local`, which Keycloak sends to the SMTP host `Mailpit`.

3. **Follow the link.** Keycloak verifies the address and has you set a password. The link then returns you to the app’s sign-in page with `returnUrl=/onboarding`.

4. **Create your company.** Onboarding registers your first company. With `Demo:Enabled` on, it gets the seeded customers and appointments, and the dashboard has data to show.

## Skip sign-up: the demo login

**`https://localhost:5173/api/v1/login/demo` signs you in as the seeded demo user, with no form and no email.**

The endpoint signs the `Demo:User` credentials in to Keycloak with a password grant, sets the app’s auth cookie and redirects to `Frontend:BaseUrl`. It’s the same flow as the public [live demo](https://app.appointme.dev/api/v1/login/demo). From there, complete onboarding to create a company.

src/AppointMe.Api/appsettings.Development.json

```json
"Demo": {
  "Enabled": true,
  "User": {
    "Email": "demo@appointme.dev",
    "Password": "AppointMe1",
    "Name": "John Doe"
  }
}
```

`appsettings.json` sets `Demo:Enabled` to `false`, so outside Development the endpoint answers `404`:

src/AppointMe.Api/Authentication/DemoLogin/DemoLoginEndpoint.cs

```cs
if (!demoOptions.Value.Enabled || demoOptions.Value.User is not { } demoUser)
{
    return TypedResults.NotFound();
}
```

In Development the demo user is also listed in `Authentication:SuperAdmins`, which is what opens the Hangfire dashboard.

## Troubleshooting

**Most first-run failures are the certificate, a port, or the API launch profile.** The full list is in [Troubleshooting](/docs/reference/troubleshooting/).

| Symptom                                                                                                   | Cause                                                                                                       | Fix                                                                                               |
| --------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| The AppHost exits with `error: the ASP.NET Core HTTPS development certificate is missing or not trusted.` | The dev certificate is missing or untrusted                                                                 | `dotnet dev-certs https --trust`. If that doesn’t help, `--clean` first, then `--trust`.          |
| `appointme-api` stays in **Waiting**                                                                      | It waits for SQL Server, Keycloak and Mailpit, and the first run is still pulling images                    | Wait, or open each container’s logs in the Aspire dashboard                                       |
| `Bind for 0.0.0.0:60740 failed: port is already allocated`                                                | Aspire and Compose use the same host ports, and Aspire’s containers keep running after you stop the AppHost | Stop one set: `docker compose stop`, or stop the Aspire containers in Docker                      |
| Under Compose, the app can’t reach the API                                                                | The API was started without `--launch-profile https`                                                        | Restart it with `--launch-profile https`                                                          |
| Under Compose, Keycloak never turns `healthy`                                                             | `docker/keycloak/certs/keycloak.crt` and `keycloak.key` don’t exist yet                                     | Run `./docker/keycloak/export-dev-cert.sh`, then `docker compose up -d --force-recreate keycloak` |
| `/api/v1/login/demo` returns `404`                                                                        | `Demo:Enabled` is `false`, or there’s no `Demo:User`                                                        | Set both in `appsettings.Development.json`                                                        |
| The generated solution doesn’t compile                                                                    | `-n` wasn’t a dotted identifier                                                                             | Generate again with a name like `Contoso.Booking`                                                 |

### Why does the AppHost refuse to start without a trusted certificate?

Without a trusted dev certificate, the API’s `https` launch profile can’t start, and Aspire quietly serves Keycloak over plain HTTP. Nothing fails at startup. The first sign of trouble is an unexplained 500 from the login endpoint, much later.

Running `dotnet dev-certs https --check --trust` up front turns that into an error that names the fix. Linux has no single trust store the tool can check, so a failure there is often a false negative, and the AppHost only warns.

## Next

* [Tour of the codebase](/docs/start/codebase-tour/) (Overview)
* [What to change first](/docs/start/what-to-change-first/) (Guide)
* [Build your first feature](/docs/start/first-feature/) (Tutorial)
