
A full-stack workspace and card management app with boards, lists, comments, attachments, pricing tools, CLI commands, and automated tests. Built with React, Bun/TypeScript, and an extensible server/client architecture for fast product iteration.
Developed by Journeyhorizon: https://journeyh.io/
We built it to solve a real problem inside our own company.
Now, it’s yours to use, customize, and grow with.
Contact us for Self-hosted with support or Cloud Host
# 1. Install dependencies
bun install
# 2. Copy environment config and edit as needed
cp .env.example .env
# 3. Build + start Postgres (pg_cron image built on first run, ~20 s) + LocalStack S3
docker compose up -d postgres localstack
# 4. Run database migrations
bun run db:migrate
# Optionally, when you have the seeds file to test
# Put this files in db/all_trello_cards.json
bun run db:seed:trello
# 5. Start the dev server (hot-reload)
bun run dev:local
curl http://localhost:3000/health→{ "status": "ok" }
docker compose --profile redis up -d
Set FLAG_USE_REDIS=true (or remove the flag) in .env to enable the Redis adapter.
Feature flags are resolved from multiple sources (lowest → highest priority):
server/mods/flags/defaults.ts)FEATURE_FLAGS_JSON_PATH=/config/flags.json)FLAG_<KEY>=true|false)FEATURE_FLAGS_PROVIDER)# Run without Redis
FLAG_USE_REDIS=false bun run dev
# Run without virus scanning
FLAG_VIRUS_SCAN_ENABLED=false bun run dev
# Use Bun Worker scheduler instead of pg_cron (default for local dev)
AUTOMATION_USE_PGCRON=false bun run dev
| Command | Description |
|---|---|
bun run dev |
Start with hot-reload |
bun run start |
Start production server |
bun run build |
Build client (Vite) + typecheck server |
bun run lint |
Run ESLint |
bun run typecheck |
Run TypeScript type checking |
bun run db:migrate |
Run database migrations |
bun run db:seed:trello |
Seed database from Trello export |
bun test |
Run all tests |
bun run docker:build |
Build the production Docker image |
bun run docker:prod |
Run the production image via docker-compose |
Option A — native Bun (no Docker):
# Build Vite client bundle + typecheck
bun run build
# Start server in production mode
bun run start
Option B — Docker (closest to real production):
# Build the production image (multi-stage: deps → build → runtime)
bun run docker:build
# Run with the prod compose file (uses .env for config)
bun run docker:prod
Make sure
.envcontains all required variables (see.env.example) before running either option.
The production image is a single self-contained Docker container (Vite client bundle + Bun server). External services — Postgres (RDS) and S3 — are referenced by URL via environment variables; nothing is co-located with the app container.
bun install --frozen-lockfilebun run build:clientnode_modules, server/, db/, dist/, and entrypoint.sh into a minimal Alpine image; runs as a non-root user# Authenticate to ECR
aws ecr get-login-password --region <region> \
| docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com
# Build — typecheck + Vite bundle happen inside the multi-stage Dockerfile
docker build -t chimedeck-app:${IMAGE_TAG} .
# Tag and push
docker tag chimedeck-app:${IMAGE_TAG} <account>.dkr.ecr.<region>.amazonaws.com/chimedeck-app:${IMAGE_TAG}
docker push <account>.dkr.ecr.<region>.amazonaws.com/chimedeck-app:${IMAGE_TAG}
The host only needs two files — no source code required:
| File | How it gets there |
|---|---|
docker-compose.prod.yml |
scp'd once, or kept in a separate deploy repo |
.env.production |
Created manually on the host (contains secrets — never commit this) |
docker-compose.prod.yml references the ECR image by name/tag via image: "${DOCKER_IMAGE}:${IMAGE_TAG}". Compose pulls it from ECR and starts it — no build step on the host.
# 1. Authenticate to ECR
aws ecr get-login-password --region <region> \
| docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com
# 2. Run database migrations (one-off container — Compose pulls the image here)
docker run --rm --env-file .env.production \
<account>.dkr.ecr.<region>.amazonaws.com/chimedeck-app:${IMAGE_TAG} \
bun run db:migrate
# 3. (One-time, local-db profile only) Activate pg_cron after the first migration
# Skip this step when using external RDS — configure pg_cron there separately.
docker compose -f docker-compose.prod.yml --profile local-db exec postgres \
psql -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \
-c "CREATE EXTENSION IF NOT EXISTS pg_cron;" \
-c "SELECT cron.schedule('automation-tick','* * * * *',\$\$SELECT automation_scheduler_tick()\$\$);"
# 4. Start the app (Compose reuses the already-pulled image)
#
# Default — external RDS + S3 (AWS managed):
DOCKER_IMAGE=<account>.dkr.ecr.<region>.amazonaws.com/chimedeck-app \
IMAGE_TAG=${IMAGE_TAG} \
docker compose -f docker-compose.prod.yml up -d
# With internal Postgres instead of RDS (builds Dockerfile.postgres on first run, ~20 s):
docker compose -f docker-compose.prod.yml --profile local-db up -d
# With LocalStack instead of AWS S3:
docker compose -f docker-compose.prod.yml --profile local-s3 up -d
# With Redis sidecar:
docker compose -f docker-compose.prod.yml --profile redis up -d
# Combining profiles:
docker compose -f docker-compose.prod.yml --profile local-db --profile local-s3 --profile redis up -d
The app connects to AWS RDS via
DATABASE_URLand to AWS S3 viaS3_BUCKET/AWS_*credentials — no database or S3 container runs on the host.
Copy .env.example to .env.production on the host and fill in all values.
cp .env.example .env.production
Key production-only variables:
| Variable | Description |
|---|---|
DATABASE_URL |
Full Postgres connection string (e.g. RDS endpoint) |
S3_BUCKET |
AWS S3 bucket name |
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY |
IAM credentials for S3 |
AWS_REGION |
AWS region |
| Variable | Default | Description |
|---|---|---|
SENTRY_SERVER_ENABLED |
false |
Enable Sentry capture on the Bun server |
SENTRY_SERVER_DSN |
(empty) | Sentry DSN for the server — obtain from your Sentry project settings |
VITE_SENTRY_CLIENT_ENABLED |
false |
Enable Sentry capture in the React client |
VITE_SENTRY_CLIENT_DSN |
(empty) | Sentry DSN for the client |
SENTRY_ENV / VITE_SENTRY_ENV |
development |
Deployment environment tag sent to Sentry — must match on both client and server for source-map lookup to work (e.g. production, staging) |
SENTRY_RELEASE / VITE_SENTRY_RELEASE |
(empty) | Release identifier (git SHA or semver tag). Must be identical to the value used during build:deploy for source-map correlation |
VITE_SENTRY_REPLAY_ENABLED |
false |
Opt-in Session Replay for the React client (bandwidth-intensive) |
Both *_ENABLED flags must be true and the corresponding *_DSN must be non-empty for Sentry to initialise. Omitting or leaving either blank is safe — the app starts normally without capture.
Sentry is configured with strict privacy defaults that apply automatically — no extra setup required:
| Protection | Default |
|---|---|
| Auto-PII collection | Disabled (sendDefaultPii: false) |
| Authorization / Cookie headers | Redacted to [redacted] by beforeSend |
Sensitive URL query-params (token, access_token, password, secret, …) |
Redacted to [redacted] by beforeSend |
Request body credentials (password, token, credit_card, …) |
Redacted server-side by beforeSend |
| Session Replay text / media | Fully masked (maskAllText, blockAllMedia) |
Override caveat: if you call
captureMessageorcaptureErrorwith manually constructed strings that include PII, the automatic redaction will not save you. Always sanitize data before passing it to Sentry helpers.
Source maps are generated on every build (vite build). The Sentry Vite plugin uploads them to Sentry and then deletes the .map files from dist/ so they are never served publicly.
The upload is gated: it only runs when all three of the following env vars are present at build time:
| Build-time variable | Description |
|---|---|
SENTRY_AUTH_TOKEN |
Sentry API auth token with project:releases scope |
SENTRY_ORG |
Your Sentry organisation slug |
SENTRY_PROJECT |
Your Sentry project slug |
When any of these is absent (e.g. in local dev or plain CI runs) the plugin is omitted entirely — no network calls are made and no .map files are deleted.
Deploy build command (CI/CD pipeline):
# SENTRY_RELEASE is derived from the git SHA automatically if not set explicitly
SENTRY_AUTH_TOKEN=<token> \
SENTRY_ORG=<org> \
SENTRY_PROJECT=<project> \
SENTRY_RELEASE=$(git rev-parse --short HEAD) \
VITE_SENTRY_RELEASE=$(git rev-parse --short HEAD) \
bun run build:deploy
build:deploy is equivalent to build but pre-seeds SENTRY_RELEASE from the current git SHA so the runtime SDK and the source-map upload always use the same identifier.
Local dev: run
bun run build:clientorbun run devas usual — no upload, no token required.
The React client initialises Sentry in src/main.tsx via initSentry() (defined in src/common/monitoring/sentryClient.ts).
Initialisation is guarded — Sentry is a no-op when VITE_SENTRY_CLIENT_ENABLED is false or VITE_SENTRY_CLIENT_DSN is empty.
Manual smoke test checklist:
VITE_SENTRY_CLIENT_ENABLED=true and a valid VITE_SENTRY_CLIENT_DSN in .env.bun run dev.throw new Error('test')).VITE_SENTRY_CLIENT_ENABLED=false, restart, and confirm no Sentry network requests are made (check the Network tab).See src/common/monitoring/README.md for full usage and privacy guidance.
| Variable | Default | Effect |
|---|---|---|
SEED_TRELLO |
false |
Runs bun run db:seed:trello before the server starts |
AUTOMATION_USE_PGCRON |
false |
true enables pg_cron scheduling; false uses Bun Worker (setInterval) fallback — the fallback does not survive restarts so use true in production |
Set SEED_TRELLO=true in .env.production for the first deployment to import Trello data, then set it back to false.
A GitHub Copilot agent loop for building Sharetribe Horizon extensions iteratively. Each run cycles through Recap → Planning → Execute → Retest → Changelog with mandatory test creation for new flows and changelog documentation for all edits.
gh copilot --version)bash 4+Open specs/architecture/requirements.md and replace every (fill in) placeholder with real content:
This file is the agent's primary source of intent. The more detail you provide, the better the generated architecture will be.
⚠
$ claude mcp add chimedeck \
-- python -m otcore.mcp_server <graph>