A tool to make board games for Print & Play and Tabletop Simulator.
Designing board games is hard. It requires rapid iteration and the ability to make changes on-the-fly. CardGoose makes it easier with data-driven components and custom templates to build your ideas in minutes.
- Import data from Google Sheets & refresh for live updates
- Component layout editor for card design
- Deck preview to evaluate changes
- Custom images
- PDF export
- TTS export (coming soon)
- Node.js 20+
- pnpm 9+
- Python 3.12+ recommended β for the PDF worker:
cd worker && python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt && playwright install chromium - AWS CLI β for production operations (e.g. Prisma against RDS); not needed for fully local dev
- Docker β fully local dev (Postgres + LocalStack)
- Terraform 1.9+ β production infrastructure (
infra/envs/prod)
Develop against Docker Postgres and LocalStack (S3/SQS). No real AWS calls. When you are ready, push to main and GitHub Actions builds and deploys to ECS.
| Piece | Where it runs locally |
|---|---|
| Frontend + API + worker | Your machine |
| Database | Docker Postgres (localhost:5433) |
| S3 / SQS | LocalStack (localhost:4566) |
The API container can serve the built SPA in production (NODE_ENV=production). Until you add a stable URL (ALB, CloudFront, etc.), you may use the task public IP for smoke tests.
-
One-time: copy
.env.local.exampleto.env.localat the repo root. It sets LocalStack (AWS_ENDPOINT_URL), Docker Postgres on 5433, andcardgoose-*buckets/queue names that matchdocker-compose.ymlanddocker/localstack-ready.d/init-aws.sh. OptionalCARDGOOSE_DEV_PROFILE=fully-localmakes the API exit on startup if values look like real AWS/RDS by mistake. -
Start the app (Postgres + LocalStack, then API + Vite):
pnpm dev:localOr start backing services only, then run processes yourself:
pnpm docker:up
pnpm migrate:deploy
pnpm dev:api
pnpm dev:frontendpnpm docker:up and pnpm docker:up:local are equivalent (Postgres on 5433, LocalStack on 4566).
- First run / after schema changes:
pnpm migrate:deploy- Open the URL Vite prints (often
http://localhost:5173).
PDF exports: use pnpm dev:local:worker to run API + Vite + the Python worker together, or run the worker in another terminal (see worker/README.md). Set RENDER_URL in .env.local to the exact origin Vite prints (including port). If the worker runs in Docker and Vite on the host, use something like http://host.docker.internal:5173.
Do not set VITE_API_URL in frontend/.env.local when the dev server should proxy /api and /health to http://localhost:3001 (see frontend/vite.config.ts).
Optional β UI only against a deployed API: copy frontend/.env.local.example to frontend/.env.local, set VITE_API_URL to your ECS task URL, run pnpm dev:frontend. You may need CORS configured on the deployed API for http://localhost:5173.
Troubleshooting (local): After a Docker / LocalStack restart, S3 buckets may be missing; the dev API creates missing S3_BUCKET_* buckets when AWS_ENDPOINT_URL points at LocalStack (4566). If you still see NoSuchBucket, seed manually, then restart pnpm dev:local:
aws --endpoint-url=http://localhost:4566 s3 mb s3://cardgoose-assets 2>/dev/null || true
aws --endpoint-url=http://localhost:4566 s3 mb s3://cardgoose-exports 2>/dev/null || true
aws --endpoint-url=http://localhost:4566 sqs create-queue --queue-name cardgoose-pdf-generation 2>/dev/null || trueOr: docker compose restart localstack (from the repo root, with compose services up).
Production means the API and worker run on ECS Fargate, with RDS, S3, and SQS from Terraform. CI deploys on push to main (see .github/workflows/ci.yml).
-
Infrastructure β follow infra/BOOTSTRAP.md and apply
infra/envs/prod. -
Prisma against RDS (from your laptop) β copy
.env.production.exampleto.env.production(never commit) withDATABASE_URLand AWS resource names matching Terraform outputs. Then:
pnpm migrate:deploy:prodECS tasks do not read .env.production; they get env from Terraform.
-
Deploy β push to
main: lint, tests, Docker builds, ECR push, ECS rolling update. Run the same checks locally first:pnpm run format:check,pnpm -r run lint,pnpm test:all. -
Smoke β
GET /healthon a running API task;serviceshould becardgoose-api. Confirm whether the API container runs migrations on startup so you do not apply twice (api/Dockerfile).
| File | Purpose |
|---|---|
.env.local.example |
Template β .env.local β local dev (Docker Postgres + LocalStack). Used by pnpm dev:local, pnpm dev:api, pnpm migrate:deploy. |
.env.production.example |
Template β .env.production β only for pnpm migrate:deploy:prod against RDS. |
The API loads .env.local via dotenv-cli on pnpm dev scripts. The worker loads .env.local when run locally (worker/README.md).
If you still have a root .env from an older setup, merge into .env.local and remove .env.
Docker Compose API: docker compose up api runs prisma migrate deploy before node (see docker-compose.yml).
pnpm run format:check
pnpm -r run lint
pnpm test:all
cd infra && terraform fmt -check -recursive
cd infra/envs/prod && terraform init -input=false && terraform validate