After cloning the repo, run bootstrap script from the repo root:
# ./berkeleytime
bash apps/docs/src/getting-started/bootstrap-local.sh Optional flags:
# Skip database seeding
bash apps/docs/src/getting-started/bootstrap-local.sh --no-seed-db
# Don't start Docker services
bash apps/docs/src/getting-started/bootstrap-local.sh --no-dockerIf the script completes successfully, your local development environment is fully set up. You don't need to run any of the manual steps below until GraphQL typedefs change or a new dependency is added.
Note: The script is for macOS and Linux/WSL.
The steps below are the manual alternative to the bootstrap script. Use them only if you prefer to set up manually or if the script fails.
Local development has a few local dependencies:
- Git
- Node version manager
- Docker Desktop
- pre-commit
First, set up Node locally:
nvm install --ltsAfter installing these dependencies, make sure you are on the main branch:
# ./berkeleytime
git pull
git switch main
# Continue installation of dependencies.
pre-commit install
# Create .env from template file
cp .env.template .env
# Setup local code editor intellisense.
npm install
npx turbo run generateOpen the docker desktop application, then run:
# Start up application
docker compose up -dThe Berkeleytime application should now be running locally at http://localhost:3000! Make sure that each page (catalog, grades, etc.) is working as expected.
Upon changing any GraphQL typedefs in the backend, the generated types must be regenerated:
# ./berkeleytime
npx turbo run generateErrors can occur when installing new npm packages. If they aren't automatically reflected in an already running docker compose:
docker compose down
docker compose up --build -dBy default, running docker compose up -d starts only the core stack (backend, frontend, MongoDB, Redis). Additional services are opt-in and can be enabled using Docker Compose profiles.
Profiles allow you to start only the services you need for your workflow, keeping local development less resource-intensive.
-
ag— AG frontend
→ http://localhost:3001 -
staff— Staff dashboard
→ http://localhost:3002 -
semantic-search— Semantic course search
→ http://localhost:3010 -
docs— Docs + Storybook
→ http://localhost:3003 / http://localhost:3005 -
dev— MinIO (staff photo uploads)
→ http://localhost:3006
# Start core + staff dashboard
docker compose --profile staff up -d
# Start multiple profiles
docker compose --profile ag --profile staff up -ddocker compose up will automatically setup certain services on your localhost ports. By default, DEV_PORT_PREFIX is set to 30, which means services will be available on ports starting with 30XX. You can adjust this by setting the DEV_PORT_PREFIX environment variable if you need to run multiple instances of the repository in parallel (e.g., for git worktree setups).
The following ports are used by default (DEV_PORT_PREFIX=30):
- 3000: Main frontend and backend API (via nginx)
- 3001: AG frontend (via nginx)
- 3002: Staff frontend (via nginx)
- 3003: Docs
- 3004: Redis
- 3005: Storybook
- 3006: MinIO API (requires
--profile dev) - 3007: MinIO Console (requires
--profile dev) - 3008: MongoDB
- 3009: API Sandbox (requires SIS API keys)
To use a different port prefix, set the DEV_PORT_PREFIX environment variable before running docker compose up:
DEV_PORT_PREFIX=80 docker compose up -dNote: Currently only
DEV_PORT_PREFIX=30(default) andDEV_PORT_PREFIX=80are fully supported. Additional port prefixes require updating the Google Cloud OAuth authorized redirect URIs.
A seeded database is required for some pages on the frontend. The bootstrap script handles this by default (use --no-seed-db to skip). The steps below are the manual alternative:
# ./berkeleytime
# Ensure the MongoDB instance is already running.
docker compose up -d
# Download the newest available public backup (publish time is not reliable)
for days_ago in 0 1 2; do
if date --version >/dev/null 2>&1; then
d=$(TZ=America/Los_Angeles date -d "${days_ago} days ago" +%Y%m%d)
else
d=$(TZ=America/Los_Angeles date -v "-${days_ago}d" +%Y%m%d)
fi
url="https://backups.berkeleytime.com/public/daily/prod_public_backup-${d}.gz"
if curl -fL -o "prod-backup.gz" "$url"; then
echo "Downloaded ${d}"
break
fi
done
# Copy the data and restore catalog/enrollment collections only.
# Exclude local auth + user-owned data (public dumps omit these; private dumps must not clobber them).
docker cp ./prod-backup.gz berkeleytime-mongodb-1:/tmp/prod-backup.gz
docker exec berkeleytime-mongodb-1 mongorestore --drop --gzip \
--archive=/tmp/prod-backup.gz \
--nsExclude=bt.users \
--nsExclude=bt.schedules \
--nsExclude=bt.collections \
--nsExclude=bt.pods \
--nsExclude=bt.ratings \
--nsExclude=bt.reviews \
--nsExclude=bt.plans
docker exec berkeleytime-mongodb-1 mongosh bt --eval 'const r = db.users.findOneAndUpdate({ email: "dev@berkeleytime.local" }, { $setOnInsert: { googleId: "dev-fake-public-backup", email: "dev@berkeleytime.local", name: "Dev User", staff: false, lastSeenAt: new Date() } }, { upsert: true, returnDocument: "after" }); print("Dev user id: " + r._id); print("Login URL: http://localhost:3000/api/dev/login?userId=" + r._id + "&redirect_uri=/");'Note: Public backups are redacted and are not a comprehensive dataset. Use private backups (Cloudflare Access required) for full data.