An agile retrospective application. | Read-only mirror of https://github.com/republique-et-canton-de-geneve/RetroGemini — République et canton de Genève. Issues & pull requests at the source.
  • TypeScript 82.9%
  • JavaScript 16.5%
  • Shell 0.4%
  • CSS 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dependabot[bot] 2c6522e729
npm(deps-dev): Bump the development-dependencies group with 2 updates (#435)
Bumps the development-dependencies group with 2 updates: [@testing-library/user-event](https://github.com/testing-library/user-event) and [eslint](https://github.com/eslint/eslint).


Updates `@testing-library/user-event` from 14.6.5 to 14.6.6
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.6.5...v14.6.6)

Updates `eslint` from 10.8.1 to 10.9.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.1...v10.9.0)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-version: 14.6.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 10.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-25 06:11:44 +00:00
.claude fix: Address the seven Codex findings on PR #417, and record D14-D16 2026-08-06 07:47:02 +00:00
.github fix: Close the three Codex findings on PR #434 (H50, H47) 2026-08-24 10:18:07 +00:00
.husky
__tests__ fix: Close the three Codex findings on PR #434 (H50, H47) 2026-08-24 10:18:07 +00:00
components feat: Reduce the Discuss vote indicator to a per-card badge 2026-08-19 14:53:39 +00:00
e2e test: Measure the accessibility baseline, and close out the pass (H42, H38) 2026-08-24 09:32:24 +00:00
e2e-prod security: Send security headers with an enforcing CSP, and pin trivy (H36, H37) 2026-08-06 08:05:06 +00:00
k8s fix: Make a lost cross-pod Socket.IO adapter visible, and heal it (H50) 2026-08-24 08:36:54 +00:00
loadtest fix: Leave only the named session, and never emit on a downed socket 2026-07-30 06:54:46 +00:00
public feat: self-host all external resources for offline/air-gapped deployment 2026-04-09 14:46:14 +00:00
scripts fix: Close the three Codex findings on PR #434 (H50, H47) 2026-08-24 10:18:07 +00:00
server fix: Close the three Codex findings on PR #434 (H50, H47) 2026-08-24 10:18:07 +00:00
services improve: Require 12-character team passwords, enforced from one shared module (H39) 2026-08-06 14:09:57 +00:00
utils improve: Set the password minimum at 8 rather than 12 (decision D18) 2026-08-07 06:40:19 +00:00
.dockerignore
.env.example revert: Keep SOCKET_UPDATE_RATE at 0, and document when to enable it (D17) 2026-08-06 09:31:46 +00:00
.gitattributes
.gitignore chore: add Playwright artifacts to .gitignore 2026-02-25 15:38:38 +00:00
.trivyignore
AGENTS.md test: Measure the accessibility baseline, and close out the pass (H42, H38) 2026-08-24 09:32:24 +00:00
App.tsx fix: Tell a refused session join apart from a dropped connection (H12) 2026-07-29 07:46:58 +00:00
AUDIT_REPORT.md docs: Align repo documentation with current reality 2026-07-09 11:57:14 +00:00
CHANGELOG.md chore: Release this change as 29.1, 29.0 being already out 2026-08-19 15:22:51 +00:00
CLAUDE.md docs: add gstack tooling and unify AGENTS.md/CLAUDE.md as single source 2026-06-30 11:40:39 +00:00
CONTRIBUTING.md docs: add gstack tooling and unify AGENTS.md/CLAUDE.md as single source 2026-06-30 11:40:39 +00:00
docker-compose.yml feat: Add automated data backup system with server-side scheduling and manual checkpoints 2026-02-26 12:46:40 +00:00
docker-entrypoint.sh
Dockerfile refactor: Type the session CAS stamp and unify socketAdapter (audit L1) 2026-07-28 11:48:20 +00:00
Dockerfile.dev docker(deps): bump node from 20-alpine to 26-alpine 2026-05-18 07:25:51 +00:00
eslint.config.js test: Measure the accessibility baseline, and close out the pass (H42, H38) 2026-08-24 09:32:24 +00:00
HARDENING_STATUS.md fix: Close the three Codex findings on PR #434 (H50, H47) 2026-08-24 10:18:07 +00:00
index.css feat: Add live typing cues and ticket-contribution dots to participants panel 2026-06-29 12:19:48 +00:00
index.html
index.tsx
LICENSE
MAINTENANCE.md refactor: Type the session CAS stamp and unify socketAdapter (audit L1) 2026-07-28 11:48:20 +00:00
nginx.conf
nixpacks.toml
package-lock.json npm(deps-dev): Bump the development-dependencies group with 2 updates (#435) 2026-08-25 06:11:44 +00:00
package.json npm(deps-dev): Bump the development-dependencies group with 2 updates (#435) 2026-08-25 06:11:44 +00:00
playwright.config.ts feat: Show ticket origin column across grouping, track departed participants and pending invitees 2026-07-06 07:14:07 +00:00
playwright.prod.config.ts security: Send security headers with an enforcing CSP, and pin trivy (H36, H37) 2026-08-06 08:05:06 +00:00
postcss.config.js ci(deps): upgrade dependabot PRs - ESLint 10, Tailwind CSS 4, Docker actions 2026-04-08 08:57:53 +00:00
QUICK_START_MAINTENANCE.md
railway.toml
README.md revert: Keep SOCKET_UPDATE_RATE at 0, and document when to enable it (D17) 2026-08-06 09:31:46 +00:00
render.yaml docs: prepare RetroGemini for public discovery (#385) 2026-07-24 15:12:34 +02:00
retrogeminihardeningaudit.md audit result gstack & claude 2026-07-07 09:09:31 +02:00
SECURITY.md test: Measure the accessibility baseline, and close out the pass (H42, H38) 2026-08-24 09:32:24 +00:00
server.js fix: Make a lost cross-pod Socket.IO adapter visible, and heal it (H50) 2026-08-24 08:36:54 +00:00
tsconfig.json refactor: upgrade major dev dependencies for Vite 8 ecosystem 2026-04-09 14:30:57 +00:00
types.ts refactor: Type the session CAS stamp and unify socketAdapter (audit L1) 2026-07-28 11:48:20 +00:00
VERSION test: Measure the accessibility baseline, and close out the pass (H42, H38) 2026-08-24 09:32:24 +00:00
vite.config.ts refactor: upgrade major dev dependencies for Vite 8 ecosystem 2026-04-09 14:30:57 +00:00
vitest.config.ts security: Send security headers with an enforcing CSP, and pin trivy (H36, H37) 2026-08-06 08:05:06 +00:00
vitest.setup.ts fix: make Web Storage tests pass on Node >= 26 in CI matrix 2026-07-22 15:15:15 +00:00

RetroGemini

CI Docker Pulls License: Unlicense

Deploy to Render

Private-by-design, real-time Scrum retrospectives and team health checks for organizations that need to keep their data on their own infrastructure.

RetroGemini was created at the State of Geneva and is already used by around 30 internal teams. Because candid retrospective feedback can be sensitive, our public administration cannot send it to external cloud services. RetroGemini keeps the complete application and its data under the organization's control.

This makes it a strong fit for public-sector, healthcare, finance, regulated, privacy-conscious, and air-gapped environments — or simply for teams that prefer to own their tools and data.

No external service is required at runtime. SMTP and OpenAI-compatible AI features are optional and disabled unless you configure them.

If RetroGemini solves a problem for your organization, please star the repository so more self-hosting and Agile teams can find it.

Try it in 60 seconds

You only need Docker:

docker run --pull=always -d \
  --name retrogemini \
  -p 8080:8080 \
  -v retro-data:/data \
  jpfroud/retrogemini:latest

Open http://localhost:8080, create a team, and start a retrospective.

The named Docker volume keeps your test data across container restarts. For a production deployment, also set a stable SESSION_TOKEN_SECRET, use HTTPS through your reverse proxy, and review the configuration below.

One-click temporary demo

The Deploy to Render button above creates a free personal evaluation instance from the latest published Docker image.

Render's free web services sleep after 15 minutes without traffic and use an ephemeral filesystem. The demo's SQLite data is therefore cleared whenever the service sleeps, restarts, or redeploys. Use this path to explore the product, not for a real team deployment.

What teams get

Area Capabilities
Retrospectives Start/Stop/Continue, 4Ls, Mad/Sad/Glad, Sailboat, KALM, DAKI, Starfish, Rose/Thorn/Bud, Hot Air Balloon, Speed Car, Lean Coffee, Three Little Pigs, and custom templates
Guided facilitation Icebreaker, Brainstorm, Group, Vote, Discuss, Review, and Close phases, with contextual tips and timeboxes
Live collaboration Real-time WebSocket sync, participant presence, typing activity, anonymous brainstorming, comments, grouping, and voting
Continuous improvement Action proposals, assignees, carry-over between sessions, ROTI follow-up, reports, and team health trends
Team administration Password-protected workspaces, member management, invitations, favorites, search, feedback hub, backup, and restore
Optional AI OpenAI-compatible ticket grouping, retrospective summaries, and cross-retrospective release analysis
Enterprise deployment SQLite or PostgreSQL, multi-pod Socket.IO adapters, Redis support, rolling-update recovery, proxies, custom CAs, Kubernetes, and OpenShift
Privacy Self-hosted assets, no CDN dependency, offline and air-gapped operation, non-root container, and organization-controlled storage

Why RetroGemini

  • Your retrospective data stays with you. The core application makes no external service calls and can run on an isolated network.
  • It is designed for real facilitation. Sessions follow a clear workflow rather than presenting an unstructured sticky-note board.
  • It supports the work after the retro. Actions, decisions, health checks, reports, and follow-up remain visible over time.
  • It starts small and scales. Use one Docker container with SQLite, or PostgreSQL plus Redis/PostgreSQL adapters for multiple pods.
  • It has real organizational usage. The product is shaped by feedback from approximately 30 teams at the State of Geneva.
  • It is open source with a permissive public-domain license. Use, adapt, and redistribute it without vendor lock-in.

Docker Compose

git clone https://github.com/republique-et-canton-de-geneve/RetroGemini.git
cd RetroGemini
docker compose up -d app

The application is available at http://localhost:8080, with data persisted in the retro-data volume.

Production example

Generate and keep a stable secret in your secret manager, then run:

docker run --pull=always -d \
  --name retrogemini \
  --restart unless-stopped \
  -p 8080:8080 \
  -v /path/to/retrogemini-data:/data \
  -e SESSION_TOKEN_SECRET='replace-with-a-long-random-secret' \
  -e SUPER_ADMIN_PASSWORD='replace-with-a-strong-admin-password' \
  jpfroud/retrogemini:latest

Put RetroGemini behind an HTTPS reverse proxy before exposing it outside a trusted network.

Deployment options

Docker

Build the image yourself if you do not want to pull the published image:

docker build -t retrogemini .
docker run -d \
  --name retrogemini \
  -p 8080:8080 \
  -v /path/to/data:/data \
  retrogemini

Kubernetes / OpenShift

See the Kubernetes and OpenShift guide. The container runs as a non-root user and exposes /health and /ready probes.

Railway

  1. Fork this repository.
  2. Create a Railway project from your fork.
  3. Add a persistent volume mounted at /data.
  4. Deploy using the included Dockerfile and railway.toml.

Without a persistent volume, SQLite data is ephemeral and will be lost during redeployments.

Configuration

All configuration is provided through environment variables. See .env.example for the complete list.

Variable Description Default
PORT Server port 3000 (8080 in Docker)
DATABASE_URL PostgreSQL connection URL; PostgreSQL is used instead of SQLite when set SQLite
POSTGRES_HOST PostgreSQL host — the discrete alternative to DATABASE_URL, used when it is unset (what the Kubernetes manifests supply from the PostgreSQL Secret) None
POSTGRES_PORT PostgreSQL port 5432
POSTGRES_USER PostgreSQL user None
POSTGRES_PASSWORD PostgreSQL password None
POSTGRES_DB PostgreSQL database name None
DATA_STORE_PATH SQLite database path /data/data.sqlite
REDIS_URL Redis connection for the multi-pod Socket.IO adapter Disabled
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD Redis connection as discrete values, when REDIS_URL is not used Disabled
SMTP_HOST SMTP server for invitations and notifications Disabled
SMTP_PORT SMTP server port 587
SMTP_SECURE Use TLS for SMTP false
SMTP_USER SMTP username None
SMTP_PASS SMTP password None
FROM_EMAIL Sender email address SMTP_USER
SUPER_ADMIN_PASSWORD Enable the super-admin panel Disabled
SESSION_TOKEN_SECRET Stable HMAC secret for sessions, invitation credentials and the WebSocket join handshake; use the same value on every pod (without it, a restart drops live participants out of their session) Random per process
BACKUP_ENABLED Enable automatic server-side backups true
BACKUP_INTERVAL_HOURS Hours between automatic backups 24
BACKUP_MAX_COUNT Maximum automatic backups to keep 7
BACKUP_ON_STARTUP Create a backup when the server starts true
RESTORE_MAX_BODY_MB Maximum compressed restore upload size 128
RESTORE_MAX_DECOMPRESSED_MB Maximum decompressed restore size 512
AUTH_RATE_LIMIT_MAX Rejected team-create / restore-session credentials per IP per 15 minutes. Only 401 responses count, so a legitimate user is never blocked however often they reload. Counted per pod (no shared store, so a multi-pod deployment allows up to replicas × this value) 5
PUBLIC_BASE_URL Canonical public URL used to build mailed links. Required to send password-reset email — that mail carries a live token and its caller is anonymous, so without a configured origin the route answers 501 public_base_url_not_configured rather than trusting the request's Host. Invitations are authenticated and fall back to the request origin when it is unset. May include a sub-path None (invitations fall back to the request origin)
CORS_ORIGIN Restrict Socket.IO CORS to specific origin(s) *
TRUST_PROXY Express trust-proxy setting 1 in production
WIFI_SSID Wi-Fi name for an optional offline-network QR code Disabled
WIFI_PASSWORD Wi-Fi password for the optional QR code Disabled
PG_POOL_MAX PostgreSQL connections per pod; keep replicas × PG_POOL_MAX under the database's max_connections 10
SESSION_CACHE_MAX Live sessions cached in memory per pod (bounds memory only — session state is always recoverable from the database) 500
SOCKET_MAX_BUFFER_SIZE Maximum Socket.IO message size in bytes (caps one session update) 1000000
SOCKET_UPDATE_RATE Sustained session updates per second allowed per socket. Off by default and off in the shipped manifest — enable it only if the deployment is reachable beyond your internal network, or if a runaway client is actually observed. Start at 20 (timer sync is ~1/s per client). A throttled write is healed with the authoritative state and re-sent, never dropped 0 (disabled)
SOCKET_UPDATE_BURST Short update burst allowed above the sustained rate 2 × rate
LAST_CONNECTION_DEBOUNCE_MS Minimum interval between refreshes of a team's last-connection date on join (avoids a write storm when a whole session reconnects) 300000
ROSTER_BROADCAST_DEBOUNCE_MS Debounce window for coalescing roster rebroadcasts (avoids an O(N²) reconnect stampede); 0 broadcasts synchronously 250

Data persistence

RetroGemini supports two database backends:

  • SQLite for a simple single-container or single-pod deployment.
  • PostgreSQL for production and multi-pod deployments.

For SQLite, the server tries these locations in order:

  1. DATA_STORE_PATH
  2. /data/data.sqlite
  3. /tmp/data.sqlite — ephemeral
  4. ./data.sqlite

A warning is logged when ephemeral storage is used.

For multiple pods, Socket.IO uses Redis when REDIS_URL or REDIS_HOST is configured. Otherwise it uses the PostgreSQL adapter when PostgreSQL is the data store.

Corporate proxy and custom CA

export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
export NO_PROXY=localhost,127.0.0.1
export NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt

The same variables can be configured in Docker Compose, Kubernetes, or OpenShift.

Architecture

  • Frontend: React 19, TypeScript, Vite, Tailwind CSS
  • Backend: Express 5, Socket.IO 4
  • Storage: SQLite with WAL mode or PostgreSQL
  • Scale-out: Redis or PostgreSQL Socket.IO adapter
  • Container: Node 26 Alpine, non-root user

The application is designed to survive rolling updates: session state is persisted, WebSocket clients reconnect automatically, and participants rejoin their active session.

Quality and security

Every pull request runs linting, TypeScript checks, unit and integration tests, a production build, dependency review, CodeQL analysis, and container vulnerability scanning.

Useful local checks:

npm run lint
npm run type-check
npm test
npm run build
npm run test:e2e

See MAINTENANCE.md, HARDENING_STATUS.md, and SECURITY.md for details.

Development

Requirements: Node.js 22+ and npm.

npm install
npm run start

In another terminal:

npm run dev

The backend runs on port 3000 and the Vite development server on port 5173.

Built with AI assistance, not dependent on AI

RetroGemini was developed with extensive assistance from Gemini, Claude, and Codex. That describes the development process, not a runtime dependency: the application works without an internet connection or an AI service.

Administrators can optionally connect an OpenAI-compatible model for grouping suggestions and summaries. Those features remain off until explicitly configured.

Contributing

Bug reports, feature requests, documentation improvements, translations, and code contributions are welcome. Read CONTRIBUTING.md before opening a pull request.

If you deploy RetroGemini, consider opening a discussion or issue to share your environment and feedback. Real-world deployment notes help other organizations adopt it.

License

RetroGemini is released into the public domain under The Unlicense. You may use, copy, modify, and distribute it for any purpose without conditions.