Repository navigation
Containerization & Docker
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Security Best Practices
- Health Checks and Monitoring
- Troubleshooting Guide
- Image Optimization Techniques
- Multi-Platform Build Considerations
- Container Registry Integration
- Automated Image Building Processes
- Conclusion
This document provides comprehensive containerization guidance for PersonalLearningPro, focusing on the multi-stage Docker build process, development and production workflows, docker-compose orchestration for local development, and operational best practices. It explains how the current Dockerfile and docker-compose.yml enable efficient development with hot reloading, layered dependency management, and clean separation between development, build, and production stages.
The containerization setup centers on three primary files:
- Dockerfile: Defines the multi-stage build pipeline for development, build, and production.
- docker-compose.yml: Orchestrates the development environment with bind mounts for hot reload and environment injection.
- .dockerignore: Ensures only necessary files are copied into containers, optimizing build cache and reducing image size.
graph TB
A["Dockerfile<br/>Multi-stage build"] --> B["Development Stage<br/>node:20-slim"]
A --> C["Build Stage<br/>node:20-slim"]
A --> D["Production Stage<br/>node:20-slim"]
E["docker-compose.yml<br/>Local Dev Orchestration"] --> F["Service: app<br/>target: development"]
F --> G["Bind Mounts<br/>client/, server/, shared/"]
F --> H["Env Injection<br/>.env"]
F --> I["Port Mapping<br/>5001:5001"]
J[".dockerignore<br/>Optimized Copy"] --> K["Excludes<br/>node_modules, dist, logs, IDE"]
Diagram sources
- Dockerfile
- docker-compose.yml
- .dockerignore
Section sources
- Dockerfile
- docker-compose.yml
- .dockerignore
- Multi-stage Docker build:
- Development stage installs all dependencies and starts the app in development mode with hot reload capabilities.
- Build stage compiles the client and server bundles into dist artifacts.
- Production stage installs only production dependencies, copies dist, sets environment to production, and starts the server.
- docker-compose development service:
- Uses the development build target.
- Maps host source directories to container paths for live editing.
- Injects environment variables from .env.
- Preserves node_modules inside the container to avoid host interference.
- Layer caching and dependency management:
- Dependencies are copied before source code to maximize cache hits when package manifests change.
- npm ci ensures deterministic installs and faster builds.
- Environment-specific configuration:
- Port 5001 is exposed and bound for both API and Vite client.
- Vite configuration resolves aliases and outputs static assets to dist/public for production serving.
Section sources
- Dockerfile
- docker-compose.yml
- package.json
- vite.config.ts
- tsconfig.json
The containerization architecture separates concerns across stages and services:
- Development: Full source code plus all dev dependencies; Vite dev server handles hot reload.
- Build: Produces optimized client and server bundles in dist.
- Production: Minimal runtime image with only production dependencies and built artifacts.
graph TB
subgraph "Development"
DevStage["Development Stage<br/>node:20-slim"]
DevStage --> DevRun["npm run dev"]
end
subgraph "Build"
BuildStage["Build Stage<br/>node:20-slim"]
BuildStage --> BuildRun["npm run build"]
BuildRun --> Dist["dist/<br/>client assets + server bundle"]
end
subgraph "Production"
ProdStage["Production Stage<br/>node:20-slim"]
ProdStage --> CopyDist["COPY --from=build /app/dist ./dist"]
ProdStage --> ProdRun["npm run start"]
end
DevRun --> Port["Expose 5001"]
Dist --> ProdRun
ProdRun --> Port
Diagram sources
- Dockerfile
- package.json
The Dockerfile defines three stages:
- Development: Copies dependency manifests, installs dependencies, copies source, exposes port 5001, and runs the development script.
- Build: Copies manifests, installs dependencies, copies source, runs the build script, and produces dist.
- Production: Copies manifests, installs only production dependencies, copies dist from the build stage, sets NODE_ENV=production, and starts the server.
flowchart TD
Start(["Build Start"]) --> Dev["Development Stage"]
Dev --> DevInstall["Install dependencies (all)"]
Dev --> DevCopy["Copy source code"]
Dev --> DevExpose["Expose 5001"]
Dev --> DevCmd["Run dev script"]
DevCmd --> Build["Build Stage"]
Build --> BuildInstall["Install dependencies (all)"]
Build --> BuildCopy["Copy source code"]
Build --> BuildRun["Run build script"]
BuildRun --> DistArtifacts["dist produced"]
DistArtifacts --> Prod["Production Stage"]
Prod --> ProdInstall["Install dependencies (--omit=dev)"]
Prod --> ProdCopyDist["COPY dist from build"]
Prod --> ProdEnv["Set NODE_ENV=production"]
Prod --> ProdCmd["Run start script"]
ProdCmd --> End(["Serve on 5001"])
Diagram sources
- Dockerfile
- package.json
Section sources
- Dockerfile
- package.json
The docker-compose.yml defines a single service that:
- Builds using the development target.
- Maps client, server, and shared directories for hot reload.
- Mounts configuration files (Vite, Tailwind, PostCSS, TS config, theme JSON) into the container.
- Preserves node_modules inside the container to avoid host-side dependency conflicts.
- Injects environment variables from .env.
- Exposes port 5001 for the web UI and API.
sequenceDiagram
participant Dev as "Developer"
participant Compose as "docker-compose.yml"
participant App as "Service : app"
participant FS as "Host FS"
participant Ctn as "Container FS"
Dev->>Compose : docker compose up
Compose->>App : Build (target=development)
Compose->>App : Mount volumes (client/, server/, shared/)
Compose->>App : Mount configs (vite.config.ts, tailwind.config.ts, etc.)
Compose->>App : Inject env (.env)
App->>Ctn : Copy manifests + install deps
App->>Ctn : Copy source code
App->>Ctn : Run dev script (Vite + Express)
Ctn-->>Dev : Serve on 5001
Dev->>FS : Edit files (hot reload via bind mounts)
FS-->>Ctn : Changes reflected immediately
Diagram sources
- docker-compose.yml
- Dockerfile
Section sources
- docker-compose.yml
- README.md
- Dependency-first copy pattern: package.json and package-lock.json are copied before source code to leverage Docker layer caching effectively.
- npm ci is used in all stages to ensure deterministic installs and faster rebuilds.
- Development stage includes devDependencies for tooling; production stage omits devDependencies to reduce attack surface and image size.
Section sources
- Dockerfile
- Dockerfile
- Dockerfile
- package.json
- Port exposure and binding: The server listens on port 5001 and serves both API routes and the Vite-built client.
- Vite configuration: Aliases and output directory are configured for client asset resolution and production bundling.
- TypeScript configuration: Path aliases and module resolution are aligned with Vite and client/server code locations.
Section sources
- server/index.ts
- vite.config.ts
- tsconfig.json
The containerization stack depends on:
- Node.js base image for all stages.
- npm scripts defined in package.json for dev/build/start.
- docker-compose for orchestrating the development service and mounting volumes.
- .dockerignore to prevent unnecessary files from being copied into images.
graph LR
Pkg["package.json<br/>scripts/dev/build/start"] --> DF["Dockerfile<br/>stage targets"]
DF --> Dev["Development"]
DF --> Bld["Build"]
DF --> Prd["Production"]
DC["docker-compose.yml<br/>service app"] --> Dev
DC --> Bld
DC --> Prd
Ig["docker-compose.yml<br/>bind mounts"] --> Src["client/, server/, shared/"]
Ig --> Cfg["vite.config.ts, tailwind.config.ts, tsconfig.json, theme.json"]
Ig --> NM["Preserve /app/node_modules"]
Ig --> Env["env_file .env"]
Diagram sources
- package.json
- Dockerfile
- docker-compose.yml
Section sources
- package.json
- Dockerfile
- docker-compose.yml
- Optimize build cache:
- Keep package.json and package-lock.json unchanged when only source code changes to reuse dependency layers.
- Use npm ci to avoid resolving floating versions and speed up installs.
- Minimize image size:
- Omit devDependencies in production.
- Exclude build artifacts and logs from the image via .dockerignore.
- Reduce startup time:
- Use npm ci in build and production stages.
- Ensure dist is built once and reused in production.
[No sources needed since this section provides general guidance]
- Environment variables:
- Use .env for local development; ensure sensitive variables are not committed.
- SESSION_SECRET must be set in production to secure session cookies.
- Least privilege:
- Run as non-root user if extending the image (recommended).
- Secrets management:
- Avoid embedding secrets in Dockerfiles or images.
- Network exposure:
- Only expose port 5001 internally for development; restrict external access in production environments.
Section sources
- .env.example
- server/index.ts
Current configuration does not define explicit health checks. Recommended additions:
- HTTP health endpoint: Expose a lightweight GET endpoint returning 200 OK when dependencies are ready.
- Readiness probe: Wait for database connections and message store initialization before marking the service ready.
- Liveness probe: Periodic check to restart unhealthy containers automatically.
[No sources needed since this section provides general guidance]
Common issues and resolutions:
- Port conflicts on 5001:
- Change host port mapping in docker-compose.yml or stop the conflicting service.
- Hot reload not working:
- Verify bind mounts for client/, server/, and shared/ directories are present.
- Ensure node_modules is preserved inside the container (volume mount for /app/node_modules).
- Dependency mismatch:
- Clear node_modules in the container and rebuild to ensure npm ci installs correct versions.
- Environment variables not applied:
- Confirm .env is present and properly formatted; verify env_file path in docker-compose.yml.
- Build failures:
- Rebuild without cache after changing package manifests.
- Ensure dist is not ignored by .dockerignore during development.
Section sources
- docker-compose.yml
- .dockerignore
- README.md
- Multi-stage builds:
- Separate development, build, and production stages to minimize final image size.
- Layer ordering:
- Copy dependency manifests before source code to improve cache reuse.
- Dependency pruning:
- Use --omit=dev in production to exclude development-only packages.
- Ignore unnecessary files:
- Leverage .dockerignore to exclude logs, IDE folders, and build artifacts.
Section sources
- Dockerfile
- Dockerfile
- .dockerignore
- Current base image:
- node:20-slim is architecture-specific; ensure consistent platform across hosts.
- Cross-platform builds:
- Use buildx to build for different architectures (e.g., linux/amd64, linux/arm64).
- Pin Node.js version and ensure all dependencies support the target platform.
- Testing:
- Validate builds on target platforms and test hot reload and production start.
[No sources needed since this section provides general guidance]
- Tagging strategy:
- Use semantic versioning tags (e.g., v1.2.3) and latest for automated deployments.
- Push commands:
- docker tag /:
- docker push /:
- Access control:
- Configure credentials securely (e.g., CI secrets) and limit push permissions.
[No sources needed since this section provides general guidance]
- CI/CD pipeline:
- Trigger builds on pushes to main or release branches.
- Run tests and linting before building images.
- Publish images to a container registry with versioned tags.
- GitHub Actions:
- Extend the existing CLA workflow to include build and publish jobs.
- Cache dependencies to speed up builds.
Section sources
- .github/workflows/cla.yml
PersonalLearningPro’s containerization leverages a clean multi-stage Docker build and a practical docker-compose setup for efficient local development. By following the layer caching strategies, environment-specific configurations, and the recommended security and optimization practices, teams can maintain fast iteration cycles while ensuring robust production deployments.