This guide covers Docker-based development workflows for the learning platform.
Docker Compose provides a development environment with hot reload support. Create a docker-compose.yml file in the project root:
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
# Mount source code for hot reload
- ./src:/app/src
- ./exercises:/app/exercises
- ./scripts:/app/scripts
# Mount config files
- ./next.config.ts:/app/next.config.ts
- ./tsconfig.json:/app/tsconfig.json
- ./package.json:/app/package.json
# Persist database
- ./data:/app/data
# Prevent overwriting node_modules
- /app/node_modules
- /app/.next
environment:
- NODE_ENV=development
- PORT=3000
- HOST=0.0.0.0
command: npm run dev
restart: unless-stoppedStart the development environment:
docker-compose up -dView logs:
docker-compose logs -fStop services:
docker-compose downThe project includes a multi-stage Dockerfile optimized for production deployments.
Installs all dependencies (including devDependencies) and builds the Next.js application:
- Uses Node.js 24 Alpine base image
- Installs native build dependencies for better-sqlite3 (Python, make, g++, cairo, etc.)
- Runs
npm cito install exact dependency versions - Executes
npm run buildto create optimized production bundle - Creates
.next/standaloneoutput for minimal runtime image
Prepares database setup tools:
- Installs production dependencies only
- Copies Drizzle ORM schema and config
- Creates data directory for SQLite database
Final lightweight image for running the application:
- Uses minimal Alpine base with dumb-init for proper signal handling
- Creates non-root user (nextjs:nodejs) for security
- Copies only the built application and necessary files
- Runs database seed script on startup (fails gracefully if already seeded)
- Exposes port 3000
- Includes health check endpoint
Build the image manually:
docker build -t learning-platform:latest .Or use the Makefile:
make docker-buildThe SQLite database is stored in /app/data/learning-platform.db inside the container.
For persistent data across container restarts:
With Docker CLI:
docker run -d \
-p 3000:3000 \
-v learning-platform-data:/app/data \
--name learning-platform \
learning-platform:latestWith Docker Compose:
volumes:
- ./data:/app/dataThis mounts your local ./data directory to /app/data in the container.
The container automatically runs the seed script on startup:
node ./scripts/seed.js 2>/dev/null || trueThis creates tables if they don't exist. If the database is already initialized, the script exits gracefully.
When using a volume mount to ./data, you can access the database from your host:
sqlite3 data/learning-platform.dbRun queries:
SELECT * FROM modules;
SELECT * FROM exercises WHERE module = 'terraform';Backup the database:
make docker-db-backupThis creates a timestamped backup in ./backups/.
Restore from latest backup:
make docker-db-restoreSymptoms:
Error: SQLITE_CANTOPEN: unable to open database file
Cause: The data/ directory doesn't exist or the container user doesn't have write permissions.
Solution:
-
Create the data directory on host:
mkdir -p data
-
If using Docker Compose with bind mount, ensure correct permissions:
chmod 755 data
-
If running in Docker, ensure the volume is properly mounted:
docker run -v ./data:/app/data ...
Symptoms:
Error: SQLITE_BUSY: database is locked
Cause: Multiple processes are trying to write to the database simultaneously, or a previous connection didn't close properly.
Solution:
-
Restart the container:
docker restart learning-platform
-
If the issue persists, stop all containers and remove the lock:
docker-compose down rm data/learning-platform.db-shm data/learning-platform.db-wal docker-compose up -d
-
For development, ensure you're not running
npm run devon the host and in Docker simultaneously.
Symptoms:
Error: Cannot find module '@/components/...'
Cause: node_modules weren't installed inside the container, or the build failed.
Solution:
-
Rebuild the image from scratch:
docker-compose down docker-compose build --no-cache docker-compose up -d
-
Check that package.json hasn't changed. If it has, rebuild:
make docker-build
Symptoms: Code changes don't appear in the running application.
Cause: Volume mounts are not configured correctly, or Next.js hot reload isn't working.
Solution:
-
Verify volume mounts in docker-compose.yml include
./src:/app/src -
Check Next.js is running in development mode:
environment: - NODE_ENV=development command: npm run dev
-
Restart the container:
docker-compose restart
-
If using Docker Desktop on macOS/Windows, ensure file sharing is enabled for the project directory.
Symptoms:
Error: bind: address already in use
Cause: Another process is using port 3000.
Solution:
-
Stop the conflicting process:
lsof -ti:3000 | xargs kill -9
-
Or change the port in docker-compose.yml:
ports: - "3001:3000"
-
Access the app at http://localhost:3001
Symptoms:
Error: no space left on device
Cause: Docker images, containers, or volumes are consuming too much disk space.
Solution:
-
Remove unused Docker resources:
docker system prune -a
-
Remove specific volumes:
docker volume ls docker volume rm learning-platform-data
-
For macOS/Windows Docker Desktop, increase the disk size limit in Settings > Resources > Disk image size.