-
Notifications
You must be signed in to change notification settings - Fork 0
Document local development and Keycloak setup #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4d798cd
620243b
d6e07d9
1c61f7f
09e157a
b26e917
314cb63
6085fb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| name: GitHub workflow checks | ||
|
|
||
| on: | ||
| push: | ||
| paths: | ||
| - ".github/workflows/**/*.yml" | ||
| pull_request: | ||
| paths: | ||
| - ".github/workflows/**/*.yml" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| gitleaks: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
|
|
||
| steps: | ||
| - name: Check out repository | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Scan workflow changes for secrets | ||
| uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2 | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| actionlint: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Check out repository | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
|
|
||
| - name: Lint GitHub Actions workflows | ||
| uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| name: Documentation | ||
|
|
||
| on: | ||
| push: | ||
| paths: | ||
| - "docs/**/*.md" | ||
| - ".markdownlint-cli2.yaml" | ||
| - ".github/workflows/documentation.yml" | ||
| pull_request: | ||
| paths: | ||
| - "docs/**/*.md" | ||
| - ".markdownlint-cli2.yaml" | ||
| - ".github/workflows/documentation.yml" | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| markdownlint: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Check out repository | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
|
|
||
| - name: Lint documentation | ||
| uses: DavidAnson/markdownlint-cli2-action@8de2aa07cae85fd17c0b35642db70cf5495f1d25 # v24.0.0 | ||
| with: | ||
| config: .markdownlint-cli2.yaml | ||
| globs: "docs/**/*.md" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,7 @@ on: | |
| - "sonar-project.properties" | ||
| - ".sonarcloud.properties" | ||
| - ".github/dependabot.yml" | ||
| - ".github/workflows/**.yml" | ||
| - ".github/workflows/workflow.yml" | ||
| pull_request: | ||
| paths: | ||
| - "**.py" | ||
|
|
@@ -34,7 +34,7 @@ on: | |
| - "sonar-project.properties" | ||
| - ".sonarcloud.properties" | ||
| - ".github/dependabot.yml" | ||
| - ".github/workflows/**.yml" | ||
| - ".github/workflows/workflow.yml" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a PR changes Useful? React with 👍 / 👎.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a dedicated GitHub Actions lint workflow that runs whenever any workflow file changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a PR changes only one of the new workflow files, such as Useful? React with 👍 / 👎.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added secret scanning to the dedicated workflow checks. Workflow-only changes now run both Gitleaks and actionlint without triggering unrelated application tests. |
||
|
|
||
| env: | ||
| REGISTRY: ghcr.io | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| config: | ||
| default: true | ||
| MD013: false | ||
| MD033: | ||
| allowed_elements: | ||
| - br |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| # Environment Variables | ||
|
|
||
| `.env.example` is the reference for the root `.env` file. Replace any applicable `<placeholder>` value before use. | ||
|
|
||
| **Required by** describes when a value must be supplied. Compose injects several application values itself, so host-run and Compose requirements differ. | ||
|
|
||
| ## Application | ||
|
|
||
| | Key | Required by | Meaning | | ||
| | ---------- | ----------- | -------------------------------------------------------------- | | ||
| | `APP_NAME` | Optional | Name shown in FastAPI metadata. | | ||
| | `APP_HOST` | Optional | Host interface for a host-run API. Default: `127.0.0.1`. | | ||
| | `PORT` | Optional | API listening port. Default: `80`. | | ||
| | `RELOAD` | Optional | Enables Uvicorn reload for host development. Default: `False`. | | ||
|
|
||
| ## Compose | ||
|
|
||
| | Key | Required by | Meaning | | ||
| | ------------------------- | ----------- | -------------------------------------------------------------------------------------- | | ||
| | `COMPOSE_PROJECT_NAME` | Optional | Isolates Compose containers, networks, volumes, and default image names. | | ||
| | `APP_PORT` | Optional | Host port mapped to the API. Default: `80`. | | ||
| | `APP_PUBLIC_URL` | Optional | Browser-visible API origin used for the Swagger callback. Default: `http://localhost`. | | ||
| | `KEYCLOAK_PORT` | Optional | Host port mapped to Keycloak. Default: `8080`. | | ||
| | `OIDC_PUBLIC_URL` | Optional | Browser-visible Keycloak origin and token issuer. Default: `http://localhost:8080`. | | ||
| | `APP_IMAGE` | Optional | Local API image name. | | ||
| | `MIGRATION_IMAGE` | Optional | Local migration image name. | | ||
| | `POSTGRES_PASSWORD` | **Compose** | Password for the local PostgreSQL service. | | ||
| | `KEYCLOAK_ADMIN_PASSWORD` | **Compose** | Password for the local `admin` account and configuration service. | | ||
|
|
||
| A minimal Compose `.env` needs only: | ||
|
|
||
| ```dotenv | ||
| POSTGRES_PASSWORD=local-postgres | ||
| KEYCLOAK_ADMIN_PASSWORD=local-keycloak | ||
| ``` | ||
|
|
||
| ## Database | ||
|
|
||
| | Key | Required by | Meaning | | ||
| | ------------------------ | ------------ | ----------------------------------------------------------------------- | | ||
| | `DATABASE_URL` | **Host run** | PostgreSQL connection URL. Compose creates its own internal URL. | | ||
| | `DATABASE_POOL_SIZE` | Optional | Number of persistent connections in the host-run pool. Default: `5`. | | ||
| | `DATABASE_MAX_OVERFLOW` | Optional | Extra temporary connections allowed above the pool size. Default: `10`. | | ||
| | `DATABASE_ECHO` | Optional | Logs generated SQL when enabled. Default: `False`. | | ||
| | `DATABASE_POOL_PRE_PING` | Optional | Checks pooled connections before reuse. Default: `True`. | | ||
|
|
||
| ## OIDC | ||
|
|
||
| | Key | Required by | Meaning | | ||
| | ----------------------------- | ------------------- | ------------------------------------------------------------------------------------------------- | | ||
| | `OIDC_ISSUER_URL` | **Host run** | Public realm URL used for discovery and issuer validation. | | ||
| | `OIDC_INTERNAL_URL` | Optional | Internal URL used to fetch discovery metadata and signing keys while retaining the public issuer. | | ||
| | `OIDC_CLIENT_ID` | **Host run** | Audience expected in API access tokens. | | ||
| | `OIDC_DOCS_CLIENT_ID` | **Host run** | Public PKCE client used by Swagger UI. | | ||
| | `OIDC_CLIENT_SECRET` | Scenario tests only | Confidential client credential; the main API and Swagger UI do not use it. | | ||
| | `OIDC_JWKS_CACHE_TTL_SECONDS` | Optional | Signing-key cache duration in seconds. Default: `300`. | | ||
|
|
||
| Compose supplies the issuer and client IDs for its local realm. | ||
|
|
||
| ## OIDC discovery overrides | ||
|
|
||
| These keys are optional as a group. If one is set, all four must be set; partial overrides are rejected at startup. | ||
|
|
||
| | Key | Meaning | | ||
| | ----------------------------- | -------------------------------------------------- | | ||
| | `OIDC_JWKS_URI` | Signing-key endpoint used by the API. | | ||
| | `OIDC_ISSUER` | Exact issuer expected in tokens. | | ||
| | `OIDC_AUTHORIZATION_ENDPOINT` | Browser authorization endpoint used by Swagger UI. | | ||
| | `OIDC_TOKEN_ENDPOINT` | Token endpoint used by Swagger UI. | | ||
|
|
||
| The overrides bypass network discovery. They are mainly useful for tests, fixed infrastructure, or air-gapped environments. | ||
|
|
||
| ## Copy-ready local `.env` | ||
|
|
||
| This example works with the default Docker Compose stack. The values are local-development credentials and must not be reused in production. | ||
|
|
||
| ```dotenv | ||
| ## Application settings ## | ||
| APP_NAME=FastAPI Template | ||
| APP_HOST=127.0.0.1 | ||
| PORT=80 | ||
| RELOAD=False | ||
|
|
||
| ## Docker Compose settings ## | ||
| COMPOSE_PROJECT_NAME=fastapi-template-dev | ||
| APP_PORT=80 | ||
| APP_PUBLIC_URL=http://localhost | ||
| KEYCLOAK_PORT=8080 | ||
| OIDC_PUBLIC_URL=http://localhost:8080 | ||
| APP_IMAGE=fastapi-template:local | ||
| MIGRATION_IMAGE=fastapi-template-migration:local | ||
|
|
||
| POSTGRES_PASSWORD=local-postgres | ||
| KEYCLOAK_ADMIN_PASSWORD=local-keycloak-admin | ||
|
|
||
| ## Database settings ## | ||
| # Used by a host-run API; Compose injects its own internal database URL. | ||
| DATABASE_URL=postgresql+psycopg://postgres:local-postgres@localhost:5432/fastapi_db | ||
| DATABASE_POOL_SIZE=5 | ||
| DATABASE_MAX_OVERFLOW=10 | ||
| DATABASE_ECHO=False | ||
| DATABASE_POOL_PRE_PING=True | ||
|
|
||
| ## OIDC / Authentication settings ## | ||
| OIDC_ISSUER_URL=http://localhost:8080/realms/fastapi-realm | ||
| OIDC_CLIENT_ID=fastapi-client | ||
| OIDC_DOCS_CLIENT_ID=fastapi-docs | ||
| OIDC_CLIENT_SECRET=change-me | ||
| OIDC_JWKS_CACHE_TTL_SECONDS=300 | ||
| ``` | ||
|
|
||
| Save the copied block as `.env` in the repository root. The four discovery override keys can remain unset for the local stack. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Local Development | ||
|
|
||
| The project supports a host-run application and a complete Docker Compose stack. Keycloak details are documented separately in [Local Keycloak](local-keycloak.md). Environment keys are listed in [Environment Variables](environment-variables.md). | ||
|
|
||
| ## Host-run application | ||
|
|
||
| `make run` applies Alembic migrations and then starts FastAPI using `.env`: | ||
|
|
||
| ```text | ||
| Alembic migration → FastAPI process | ||
| ``` | ||
|
|
||
| PostgreSQL and the configured OIDC provider must already be reachable. Reloading is controlled by `RELOAD`; it is disabled in `.env.example`. | ||
|
|
||
| ## Compose stack | ||
|
|
||
| The local stack has five services. Solid arrows are startup gates; dotted arrows are runtime connections: | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| db["db<br/>PostgreSQL"] -->|healthy| migrate["migrate<br/>apply schema, then exit"] | ||
| migrate -->|completed| app["app<br/>FastAPI API"] | ||
|
|
||
| keycloak["keycloak<br/>local identity"] -->|healthy| config["keycloak-config<br/>set Swagger callback, then exit"] | ||
| config -->|completed| app | ||
|
|
||
| db -.->|SQL| app | ||
| keycloak -.->|OIDC and keys| app | ||
| ``` | ||
|
|
||
| - `db` is PostgreSQL and stores data in a named volume. | ||
| - `migrate` applies Alembic migrations and exits. | ||
| - `keycloak-config` adjusts the local browser callback and exits. | ||
| - `app` starts only after the database, migration, and identity services are ready. | ||
|
|
||
| `POSTGRES_PASSWORD` and `KEYCLOAK_ADMIN_PASSWORD` are required Compose values. The application database and OIDC addresses are supplied by Compose, so their container-facing hostnames differ from host-run values. | ||
|
|
||
| The application source is copied into its image rather than mounted. Code changes therefore require an image rebuild. This makes the local container match the image tested by CI. | ||
|
|
||
| ## Images and state | ||
|
|
||
| The application and migrations use separate images. The application image contains runtime code; the migration image contains Alembic and its migration-only dependencies. This keeps schema changes explicit instead of running them during application startup. | ||
|
|
||
| PostgreSQL data survives a normal `docker compose down`. Removing volumes resets it: | ||
|
|
||
| ```bash | ||
| docker compose down -v | ||
| ``` | ||
|
|
||
| ## Isolated stacks | ||
|
|
||
| `COMPOSE_PROJECT_NAME` separates container names, networks, volumes, and default image tags. Parallel stacks must also use distinct host ports and public URLs: | ||
|
|
||
| ```text | ||
| APP_PORT, APP_PUBLIC_URL, KEYCLOAK_PORT, OIDC_PUBLIC_URL | ||
| ``` | ||
|
|
||
| `APP_PUBLIC_URL` and `OIDC_PUBLIC_URL` are externally visible addresses. Compose service names such as `db` and `keycloak` are internal addresses and do not change with host ports. | ||
|
|
||
| The Compose stack is development infrastructure. Production deployments use separately managed databases, identity services, networking, secrets, and migration jobs. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Local Keycloak | ||
|
|
||
| The Compose stack runs Keycloak 26.7 in development mode. It is local test infrastructure, not a production Keycloak configuration. | ||
|
|
||
| ## Realm | ||
|
|
||
| `docker-compose.yml` starts Keycloak with `start-dev --import-realm` and imports `.docker/realm-export.json` as `fastapi-realm`. | ||
|
|
||
| Keycloak has no persistent data volume. Recreating the container resets it to the committed realm export. This keeps fresh local stacks reproducible. | ||
|
|
||
| ## Clients | ||
|
|
||
| | Client | Purpose | Configuration | | ||
| | ---------------- | ------------------ | ------------------------------------------------------------- | | ||
| | `fastapi-docs` | Swagger UI login | Public authorization-code client with PKCE; no browser secret | | ||
| | `fastapi-client` | API token audience | Confidential local client; the API validates its audience | | ||
|
|
||
| The `keycloak-config` one-shot service replaces the Swagger redirect URI and web origin with the current `APP_PUBLIC_URL`. Exact URLs are used because wildcard browser callbacks are unsafe and break isolated stacks on alternate ports. | ||
|
|
||
| The browser uses the host-facing issuer, such as `http://localhost:8080`. The API fetches Keycloak metadata and keys through the internal Compose address, `http://keycloak:8080`. Tokens still retain the public issuer. | ||
|
|
||
| ## Local identities | ||
|
|
||
| - Keycloak administrator: `admin`; its password comes from `KEYCLOAK_ADMIN_PASSWORD`. | ||
| - Local user: `testuser` / `testuser@example.com`. | ||
|
|
||
| `testuser` has no initial password. The local browser flow accepts the known username, skips the absent password check, and immediately requires the user to create a password. Recreating Keycloak removes that password. | ||
|
|
||
| This password-claiming behavior is intentional local convenience. It must not be copied into a production realm. | ||
|
|
||
| ## Scenario-test realm | ||
|
|
||
| `docker-compose.e2e.yml` replaces the normal import with `.docker/e2e-realm-export.json`. That realm is `fastapi-e2e-realm` and contains the fixed `e2e-user` identity used by automated scenario tests. Its credentials are test fixtures only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a PR changes only another workflow such as
.github/workflows/documentation.yml, this narrowed path filter prevents the CI/CD workflow's existingactionlintjob from starting; the new documentation workflow only runs markdownlint, so future edits to that workflow no longer get the repository's Actions lint check. Keep the broader workflow glob here, or add an actionlint job to the documentation workflow for workflow-file changes.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a dedicated GitHub Actions lint workflow that covers every workflow file change while keeping application CI scoped.