Skip to main content

Local Development

Hephaestus supports full-stack development across Java and TypeScript. This guide focuses on the development environment; production operations are covered in the Admin Guide.

Prerequisites

Install and configure the following tools before you attempt a local build:

  1. Java JDK 21 – Required for the Spring Boot application server.
  2. Docker Desktop – Used for PostgreSQL and NATS containers.
  3. Node.js LTS (>= 24) and pnpm (>= 11) (enable via corepack enable) – For the React client and root scripts.
  4. NATS CLI (optional) – Helpful when inspecting the agent job stream (NATS is disabled by default locally).

Open the repository using the project.code-workspace file in VS Code and install the workspace recommendations (@recommended in the Extensions view). Key extensions include:

  • Java Extension Pack
  • Spring Boot tools
  • Node.js + TypeScript tooling
  • ESLint + Prettier
  • Tailwind CSS IntelliSense

JetBrains alternatives such as IntelliJ (Java) and WebStorm (React/TypeScript) work equally well.

Application server

Maven profiles and local configuration

We ship three Maven profiles: local (default), prod, and specs. For development stick to the local profile.

Create server/src/main/resources/application-local.yml to override defaults. This file is gitignored.

:::caution Keep it local Never commit application-local.yml. It may contain secrets and machine-specific configuration. :::

Running the stack

  1. Start Docker Desktop.

  2. From the repo root, start everything with one command:

    pnpm dev

    This launches an mprocs session with the server and webapp in separate panes (switch with the arrow keys). The PostgreSQL container is brought up automatically.

    Prefer plain terminals? Run the two sides yourself:

    pnpm dev:server # terminal 1 — brings up Postgres, then Spring Boot
    pnpm dev:webapp # terminal 2 — Vite dev server
  3. Access the API at http://localhost:8080 (Swagger UI at http://localhost:8080/swagger-ui/index.html) and the webapp at http://localhost:4200.

To wipe the local database (e.g. after a bad migration or sync), run pnpm dev:reset. The Postgres data is a bind-mounted folder (server/postgres-data), so docker compose down -v alone does not clear it — dev:reset removes the folder and recreates the stack.

Port overrides

Every host-side port is configurable so multiple Hephaestus instances (or other services) can coexist on the same machine. Container-internal ports never change – only the localhost binding moves.

Default port map

ServiceDefaultVariableConfig location
PostgreSQL5432POSTGRES_PORTserver/.env
Application server8080SERVER_PORTserver/.env
Webapp (Vite dev)4200WEBAPP_PORTShell env or webapp/.env

:::tip Pre-flight check Run pnpm run check:ports before starting the stack to verify all ports are free. :::

How it works

The .env file in server/ is read by both Docker Compose (for container port mappings) and Spring Boot (via spring.config.import). This means a single file controls the entire local stack.

Ports flow through the system like this:

.env (POSTGRES_PORT=15432)
├─► Docker Compose → '15432:5432' host mapping
└─► Spring Boot → jdbc:postgresql://localhost:15432/hephaestus

Changing ports

Basic example – move PostgreSQL to a non-standard port:

# server/.env
POSTGRES_PORT=15432

The application server automatically picks up the new port from the same .env file.

Changing the application server port:

# server/.env
SERVER_PORT=18080

If the webapp also needs to reach the server at the new port, set the server URL in the webapp environment:

# webapp/.env (or shell)
VITE_APPLICATION_SERVER_URL=http://localhost:18080

Changing the webapp port:

The Vite dev server reads WEBAPP_PORT from the shell environment (not from server/.env):

WEBAPP_PORT=5200 pnpm --filter webapp run dev

When changing the webapp port, the server's CORS origin must also be updated so API calls are not rejected:

# server/.env
APPLICATION_HOST_URL=http://localhost:5200

Cascading effects reference

Changing a port in one place may require updates elsewhere. This table shows the full picture:

When you change...Also update...Why
WEBAPP_PORTAPPLICATION_HOST_URL in .envCORS origin must match
SERVER_PORTWebapp's APPLICATION_SERVER_URL (if connecting directly)API base URL changes
POSTGRES_PORTNothing (automatic)Both Compose and Spring read from .env

:::warning Webapp defaults The webapp has a hardcoded dev default in webapp/src/environment/index.ts for APPLICATION_SERVER_URL (http://localhost:8080). In production this is injected via window.__ENV__. For local development with non-standard ports, set it as a Vite environment variable or update the default. :::

GitHub configuration

Hephaestus supports two GitHub authentication modes. Pick the one that fits your workflow:

ModeBest forWorkspace creationRepository monitors
PATQuick local developmentManual (from config)Manual (you list them)
GitHub AppTesting webhooks, productionAutomatic (from installations)Automatic (from installation events)

Option A: Personal Access Token (simpler)

  1. Create a Personal Access Token with scopes: repo, read:org, read:user.

  2. Create application-local.yml:

    hephaestus:
    workspace:
    init-default: true
    default:
    login: your-github-org # e.g., "ls1intum" or "HephaestusTest"
    token: ghp_your_token
    repositories-to-monitor:
    - your-github-org/repo1
    - your-github-org/repo2
  3. Leave github.app.id unset or set to 0.

Option B: GitHub App

  1. Create a GitHub App with appropriate permissions.

  2. Create application-local.yml:

    github:
    app:
    id: 12345
    privateKey: |
    -----BEGIN RSA PRIVATE KEY-----
    ...
    -----END RSA PRIVATE KEY-----

    hephaestus:
    workspace:
    init-default: false # Workspaces created from installations
  3. Install the app on your organization. Workspaces and monitors are created automatically.

Limiting sync scope (development filters)

When using GitHub App mode, your app might have access to hundreds of repositories. Use filters to focus on specific orgs/repos during development:

hephaestus:
sync:
filters:
allowed-organizations:
- ls1intum
- HephaestusTest
allowed-repositories:
- ls1intum/Hephaestus
- ls1intum/Artemis
- HephaestusTest/demo-repository

Empty lists = no filtering (production behavior). Non-empty = only sync matching items.

tip

Filters don't delete data – they just skip sync operations. Workspace and organization metadata is still created for all installations.

Authentication (local)

Authentication is Hephaestus-native (no Keycloak — see ADR 0017). The server federates to GitHub (and optionally GitLab — gitlab.com or a self-hosted instance) via Spring Security oauth2Login, then issues its own short-lived ES256 cookie-session JWT. The webapp reads GET /user and redirects to /auth/login — there is no token in the browser.

Configure GitHub login

  1. Create a GitHub OAuth application with the callback URL http://localhost:8080/login/oauth2/code/github (use your SERVER_PORT if you overrode it).

  2. Copy server/.env.example to server/.env and set the credentials:

    cp server/.env.example server/.env
    # Edit .env and set:
    # GITHUB_OAUTH_CLIENT_ID=<github-client-id>
    # GITHUB_OAUTH_CLIENT_SECRET=<github-client-secret>
  3. (Optional) To enable GitLab login locally, register an OAuth application on your GitLab instance (callback http://localhost:8080/login/oauth2/code/gitlab, scope read_user), set GITLAB_OAUTH_CLIENT_ID / GITLAB_OAUTH_CLIENT_SECRET, and point GITLAB_OAUTH_BASE_URL at the instance (defaults to https://gitlab.com; e.g. https://gitlab.lrz.de). Leave the client id blank to hide the GitLab button.

  4. Run the application server with ./mvnw spring-boot:run; Postgres + NATS start automatically via Spring Boot's Docker Compose support. Open the webapp and click Sign in with GitHub.

Super admin

App-level admin is the APP_ADMIN role on the account table (authority admin in the JWT), managed from the /admin/users UI by an existing admin. There is no first-login auto-elevation; seed the first admin directly in the DB (UPDATE account SET app_role = 'APP_ADMIN' WHERE id = …) or via a one-off Liquibase change.

Troubleshooting auth

  • 401 after login: ensure GITHUB_OAUTH_CLIENT_ID/SECRET are set and the callback URL on the GitHub app matches your SERVER_PORT.
  • Logins don't survive a server restart locally: set HEPHAESTUS_AUTH_STATE_COOKIE_KEY (base64 32-byte; openssl rand -base64 32) in server/.env — otherwise an ephemeral per-boot key is used.
  • Reset everything: run pnpm dev:reset (Postgres is a bind-mounted folder, so docker compose down -v does not clear it — dev:reset removes server/postgres-data and recreates the stack); on next login the account + identity link are recreated.

Webhook receiver

The webhook receiver lives in the Java server (integration.core.webhook package). In local development the monolith default boots all three runtime roles (server, worker, webhook), so pnpm run dev:server exposes the unified /webhooks/{kind} endpoint (kinds: github, gitlab, slack) alongside the rest of the API.

In production the receiver is deployed as a separate webhook-server container from the same image, activated with SPRING_PROFILES_ACTIVE=prod,webhook. The app-server's deploy cycle does NOT interrupt webhook reception — push events on GitHub/GitLab are not manually redeliverable, so restart independence is operationally required. See ADR 0008.

When you run docker/compose.core.yaml set the webhook secret up front so signature checks succeed (the same secret is used by both auto-registration and the receiver):

cp docker/.env.example docker/.env
export WEBHOOK_SECRET=$(openssl rand -hex 32)
echo "WEBHOOK_SECRET=$WEBHOOK_SECRET" >> docker/.env

The value must match the secret configured on your GitHub and GitLab webhooks (validated via X-Hub-Signature-256 for GitHub, X-Gitlab-Token for GitLab).

Web client (webapp)

  1. Install dependencies:

    pnpm install
  2. Start Vite (defaults to port 4200):

    pnpm --filter webapp run dev
  3. Visit http://localhost:4200 (or your custom WEBAPP_PORT).

UI foundations

  • We ship Tailwind CSS 4 in JIT mode. Keep utility classes in JSX and rely on design tokens defined in src/styles.css.
  • Ensure Tailwind IntelliSense is enabled in your IDE – typeahead prevents typos and invalid compositions.
  • Avoid premature abstractions with @apply; duplication is fine when it keeps components readable.

Component workflow (Storybook + Chromatic)

Storybook is the source of truth for presentational components.

pnpm --filter webapp run storybook # Local playground on http://localhost:6006
pnpm --filter webapp run build-storybook # Static bundle for Chromatic
pnpm --filter webapp run chromatic:ci # Visual regression in CI
  • Every new UI component ships with at least one story covering empty, loading, and error states.
  • Chromatic runs on every pull request – review visual diffs before merging.
  • Reference ui.shadcn.com for composition patterns; stick to headless Radix primitives when you need new widgets.

OpenAPI client

The generated client in src/api is the only way the webapp talks to server services.

pnpm --filter webapp run openapi-ts
  • Generated types drop the Dto suffix and mirror the Spring Boot API responses.
  • Pair every new endpoint with a typed TanStack Query hook for caching and invalidation.
  • Never hand-roll fetch calls – shared interceptors handle auth tokens and error telemetry.