SKILL.md
When to Use
- Building the
infra/folder (Dockerfiles, compose, env templates) - Updating how frontend ↔ backend communicate in containerized environments
- Adding scripts for local Docker workflows or CI pipelines tied to infra
- Auditing existing Docker settings to keep them aligned with
docs/architecture.md
Critical Patterns
Folder Layout
infra/
├── docker-compose.yml
├── frontend.Dockerfile
├── backend.Dockerfile
└── README.md (explain ports, env, volume usage)
- Keep infra-specific assets isolated from app packages
- Compose file must live under
infra/and reference Dockerfiles via relative paths (e.g.,build: { context: .., dockerfile: infra/frontend.Dockerfile })
Service Contracts
| Service | Port (container → host) | Notes |
|---|---|---|
frontend |
3000:3000 | Runs next start; use env NEXTPUBLICAPIBASEURL=http://backend:4000 or Compose network alias |
backend |
4000:4000 | Runs Express build output (node dist/app.js) |
- No database or queue services for this challenge. Reject attempts to introduce them.
- Use Docker network defaults so
frontendcan reachhttp://backend:4000internally; do not expose backend publicly unless required.
Dockerfile Expectations
Frontend:
- Multi-stage build (
builder,runner) - Base image:
node:20-alpine(matches engines) - Copy monorepo files, install pnpm globally via corepack (or
pnpm fetch), thenpnpm --filter frontend... - Output:
.next/,node_modulesfor production only (usenext build && next start) - Set
PORT=3000,HOST=0.0.0.0
Backend:
- Multi-stage (
buildercompiles TypeScript,runnercopiesdist/) - Use
pnpm fetch+pnpm install --offlineto leverage the lockfile - Expose
PORT=4000 - Entrypoint:
node dist/app.js
Shared package should be built in the builder stage via pnpm --filter shared build before frontend/backend builds.
Compose Guardrails
- Always mount
.env.example→.envwith explicitenv_file:entries; never rely on host envs implicitly - Use
depends_onso backend is ready before frontend tries to proxy API calls - Provide a
volumes:override only for dev (e.g.,.:/app+pnpm install), but default compose stack should run the built artifacts - Document commands inside
infra/README.md
Networking & Rewrites
- Next.js should continue to proxy
/apitobackend:4000; do not open CORS on backend unless Compose rewrites prove impossible - For local Docker usage, prefer hitting
http://localhost:3000and let rewrites map to backend automatically
ALWAYS
- Build shared artifacts in dependency order (shared → backend/frontend) inside Dockerfiles
- Copy only necessary files into runtime layers (no
.git, no local dev config) - Keep Compose deterministic: no
latesttags, always build from the local source - Mirror the same Node/pnpm versions defined in
package.jsonengines
NEVER
- Add databases, queues, or extra services unless the spec explicitly changes
- Run
pnpm installtwice (usepnpm fetch+pnpm install --offlineonce per stage) - Use root user in production layers; switch to
nodeuser when possible - Bake secrets or
.envvalues directly into images—pass through env vars at runtime
Commands
# Build & run the stack locally (from repo root)
# Tear down containers and remove orphaned services
# Rebuild a single service
docker compose -f infra/docker-compose.yml build backend
# Follow logs
# Clean dangling images created during local iterations