This document outlines the recommended deployment strategy for the SDC Web React application using Docker containerization.
We will use a multi-stage Docker build that:
- Builds the React application using Node.js
- Serves the built static files using Caddy web server
- Proxies API requests to the backend service
- Small Image Size: Final image ~50MB (Caddy) vs ~1GB (Node.js)
- Production Optimized: Caddy handles compression, caching, and HTTPS automatically
- Reproducible: Same build process across all environments
- Portable: Deploy anywhere Docker runs (AWS, GCP, DigitalOcean, self-hosted)
- Backend Integration: Easy API proxying to backend service
┌─────────────┐
│ Browser │
└──────┬──────┘
│
↓
┌─────────────────────────────┐
│ Frontend Container (Caddy) │
│ ┌────────────────────────┐ │
│ │ Static Files (React) │ │ Port 80/443
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ Reverse Proxy │ ├──→ /api/* requests
│ └────────────────────────┘ │
└──────────────┬──────────────┘
│
↓
┌─────────────────────────────┐
│ Backend API Container │ Port 8000
└─────────────────────────────┘
Multi-stage build with:
- Stage 1 (builder): Node.js to build the React app
- Stage 2 (production): Caddy to serve static files
Key features:
- Use
.dockerignoreto exclude unnecessary files - Install dependencies and build the app
- Copy only the
/distfolder to final image - Configure Caddy to serve SPA and proxy API
- Extract version from package.json for image labeling
Caddy configuration for:
- Serving static files from
/dist - SPA routing (handle client-side routes)
- Reverse proxy
/api/*to backend - Compression and caching headers
- Optional HTTPS (auto with domain name)
Exclude from Docker build context:
node_modules/.git/dist/- Development files (
.env.local, etc.) - Documentation and test files
For local development/testing:
- Frontend service (this React app)
- Backend service (your API)
- Shared network for service communication
- Volume mounts for development
Remove or make conditional:
- Development-specific
allowedHostsconfiguration - Add production-specific optimizations if needed
The Docker container version should be sourced from package.json to maintain consistency between the application version and container tags.
Use the Node.js built-in require to extract the version:
# Extract version from package.json.
VERSION=$(node -p "require('./package.json').version")
echo $VERSION # Output: 0.1.0
# Build with version tag.
docker build -t sdc-web:$VERSION -t sdc-web:latest .This method:
- Works cross-platform (no additional tools needed beyond Node.js)
- Reads directly from package.json
- Keeps version in a single source of truth
Tag images with both specific version and latest:
VERSION=$(node -p "require('./package.json').version")
docker build -t sdc-web:$VERSION -t sdc-web:latest .This creates two tags:
sdc-web:0.1.0- Specific version for rollbacks and trackingsdc-web:latest- Convenient tag for development/testing
# Build the Docker image with version from package.json.
VERSION=$(node -p "require('./package.json').version")
docker build -t sdc-web:$VERSION -t sdc-web:latest .
# Run the container (use specific version for clarity).
docker run -p 8080:80 \
-e BACKEND_URL=http://backend:8000 \
sdc-web:$VERSION
# Or use docker-compose.
docker-compose up# Extract version.
VERSION=$(node -p "require('./package.json').version")
# Tag for registry.
docker tag sdc-web:$VERSION registry.example.com/sdc-web:$VERSION
docker tag sdc-web:$VERSION registry.example.com/sdc-web:latest
# Push to registry.
docker push registry.example.com/sdc-web:$VERSION
docker push registry.example.com/sdc-web:latest
# Deploy using provider's CLI/console.# Pull specific version and run on server.
VERSION=0.1.0 # Or extract from package.json
docker pull your-registry/sdc-web:$VERSION
docker run -d -p 80:80 -p 443:443 \
--name sdc-web \
-e BACKEND_URL=http://api.internal:8000 \
your-registry/sdc-web:$VERSION# Deployment manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: sdc-web
spec:
replicas: 3
template:
spec:
containers:
- name: sdc-web
image: your-registry/sdc-web:0.1.0 # Use specific version, not latest
ports:
- containerPort: 80
env:
- name: BACKEND_URL
value: "http://api-service:8000"NODE_VERSION: Node.js version for building (default: 20)CADDY_VERSION: Caddy version for serving (default: 2-alpine)
BACKEND_URL: Backend API URL (for Caddyfile proxy configuration)PORT: Port to serve on (default: 80)
The Caddyfile will proxy requests from /api/* to your backend. You need to:
- If backend is in same Docker network: Use service name (e.g.,
http://backend:8000) - If backend is external: Use full URL (e.g.,
https://api.example.com)
Your app uses:
- RTK Query with base URL
/api(src/redux/api.tsx:12) - Direct fetch API also uses
/apiprefix (src/api/fetchapi.tsx)
This means Caddy MUST proxy /api/* to your actual backend, or the app will break.
Current vite.config.ts has:
server: {
allowedHosts: ["dfmoller-acer-aspire.springbok-cod.ts.net"],
}This is development-only and should be:
- Removed for production builds
- Or made conditional based on environment
- HTTPS: Caddy auto-provisions Let's Encrypt certs if you provide a domain
- API Keys: Never bake secrets into the Docker image. Use environment variables.
- CORS: If backend is on different domain, ensure CORS headers are configured
- Rate Limiting: Consider adding rate limiting in Caddy or at load balancer level
- Compression: Caddy automatically gzips responses
- Caching: Set cache headers for static assets
- CDN: Consider putting CloudFlare or similar in front
- Multi-stage Build: Minimizes image size
- Health Checks: Add health check endpoint for orchestration
- Test Build Locally: Ensure Docker image builds successfully
- Test with Backend: Verify API proxying works
- CI/CD Integration: Set up automated builds (GitHub Actions, GitLab CI, etc.)
- Monitoring: Add logging and monitoring (Prometheus, Datadog, etc.)
- Staging Environment: Deploy to staging before production
- Documentation: Update README with deployment instructions
Pros: Easy, cheap, CDN included Cons: API proxy more complex, requires public backend or serverless functions Verdict: Not ideal due to API dependency and desire for infrastructure control
Pros: Simpler Dockerfile Cons: Much larger image (~1GB), not production-optimized for serving static files Verdict: Wasteful for a static SPA
Pros: More widely known, very mature Cons: More complex configuration, no auto-HTTPS Verdict: Caddy is simpler and more modern for this use case
-
Where is your backend API?
- Same server? Use Docker Compose with shared network
- Different server? Configure BACKEND_URL environment variable
-
Do you need HTTPS locally?
- If yes, Caddy can handle it with self-signed certs
- If no, keep it simple with HTTP on port 80
-
What's your deployment target?
- Cloud provider (AWS, GCP, Azure)?
- Self-hosted VPS?
- Kubernetes cluster?
-
Do you need CI/CD?
- GitHub Actions for automated builds?
- GitLab CI/CD?
- Jenkins?
- Create Dockerfile: 15 minutes
- Create Caddyfile: 10 minutes
- Create .dockerignore: 5 minutes
- Update vite.config.ts: 5 minutes
- Create docker-compose.yml: 10 minutes
- Test build locally: 15 minutes
- Documentation: 10 minutes
Total: ~1 hour for complete implementation and testing