Docker
Docker
Overview
The project includes a full Docker setup for running the complete stack in containers and producing production-ready images.
shiftwise/
├── docker-compose.yml # Full stack: postgres + backend + frontend
├── docker-compose.dev.yml # Dev overrides with hot reload
├── .env.example # Environment variable template
├── backend/
│ ├── Dockerfile # Multi-stage: builder + production (node/alpine)
│ └── .dockerignore
└── frontend/
├── Dockerfile # Multi-stage: builder + production (nginx/alpine)
├── nginx.conf # React Router support + asset caching
└── .dockerignore
Running locally
Prerequisites
Docker Desktop installed and running on your machine.
Step 1 — Create .env
cp .env.example .env
Fill in your values:
JWT_ACCESS_SECRET=your-64-char-hex-string
JWT_REFRESH_SECRET=your-different-64-char-hex-string
FRONTEND_URL=http://localhost:80
VITE_API_URL=http://localhost:3001
Generate JWT secrets:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Step 2 — Build and start
docker compose up --build
Three containers start:
shiftwise_db— PostgreSQL 16 on port 5432shiftwise_api— Express API on port 3001shiftwise_fe— nginx serving React on port 80
Step 3 — Migrate and seed (first time only)
docker compose exec backend sh -c "cd backend && npx prisma migrate deploy"
docker compose exec backend sh -c "cd backend && npx tsx prisma/seed.ts"
Step 4 — Open the app
Go to http://localhost:80 and log in with will.power@demo.com / password123.
Tear down and reset
# Stop containers and wipe the database volume
docker compose down -v
# Rebuild fresh
docker compose up --build -d
sleep 10
docker compose exec backend sh -c "cd backend && npx prisma migrate deploy"
docker compose exec backend sh -c "cd backend && npx tsx prisma/seed.ts"
The -v flag removes the postgres_data volume — the database starts completely empty.
Hot reload in development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
Source files are mounted as volumes so changes hot-reload without rebuilding the image.
Running in Codespaces
Note: Docker-in-Docker is not reliably supported in GitHub Codespaces. Use
npm run devfor day-to-day development in Codespaces instead.
If you do need to run Docker in Codespaces, Codespaces uses forwarded URLs
instead of localhost. Get your URLs first:
echo "Frontend: https://${CODESPACE_NAME}-80.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
echo "Backend: https://${CODESPACE_NAME}-3001.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}"
Update .env with the actual URLs (no variable expansion — paste the real values):
FRONTEND_URL=https://your-codespace-80.app.github.dev
VITE_API_URL=https://your-codespace-3001.app.github.dev
After building, go to the Ports tab in VS Code and set ports 80 and
3001 to Public visibility.
Codespaces URLs change when you create a new Codespace — update
.envand rebuild when this happens.
Multi-stage build explained
Both Dockerfiles use multi-stage builds to keep production images lean:
Backend:
Stage 1 (builder) — installs all deps, generates Prisma client, compiles TypeScript
Stage 2 (production) — installs prod deps only, copies compiled dist/
Frontend:
Stage 1 (builder) — installs all deps, runs vite build
Stage 2 (production) — nginx/alpine, copies dist/ only
The production images contain no TypeScript source, no devDependencies, and no build tools — just what’s needed to run.
Why Docker?
- Consistency — identical environment locally, in CI, and in production
- Onboarding — one command gets a new developer the full stack
- Production parity — test against the same nginx + node/alpine used in production
Tip: Use
npm run devfor active development — it’s faster and breakpoints work out of the box. Use Docker to verify production builds before deploying.