Add Docker support for client and server with Ubuntu-based images, build script, docker-compose, and comprehensive documentation

This commit is contained in:
Sean Sube 2025-06-18 21:43:34 -05:00
parent 02d4bae997
commit 599ed993d3
No known key found for this signature in database
GPG Key ID: 3EED7B957D362AF1
8 changed files with 590 additions and 1 deletions

253
DOCKER.md Normal file
View File

@ -0,0 +1,253 @@
# Docker Setup for Task Receipts
This document explains how to build and run the Task Receipts application using Docker.
## Prerequisites
- Docker installed and running
- Docker Compose (usually included with Docker Desktop)
## Quick Start
### Option 1: Using Docker Compose (Recommended)
1. **Build and run both services:**
```bash
docker-compose up --build
```
2. **Run in background:**
```bash
docker-compose up -d --build
```
3. **Stop services:**
```bash
docker-compose down
```
### Option 2: Using the Build Script
1. **Build both images:**
```bash
./build-docker.sh
```
2. **Build with specific version:**
```bash
./build-docker.sh v1.0.0
```
3. **Run containers individually:**
```bash
# Run server
docker run -p 4000:4000 task-receipts-server:latest
# Run client (in another terminal)
docker run -p 80:80 task-receipts-client:latest
```
## Services
### Server
- **Port:** 4000
- **Health Check:** GraphQL endpoint at `/graphql`
- **Database:** SQLite (persisted in `./server/data`)
- **Features:**
- GraphQL API
- YAML import/export
- Receipt printing
- Database management
### Client
- **Port:** 80
- **Web Server:** Nginx
- **Features:**
- React application
- Material-UI components
- Apollo Client for GraphQL
## Docker Images
### Client Image (`task-receipts-client`)
- **Base:** `nginx:stable` (Ubuntu-based)
- **Build:** Multi-stage build with Node.js 18 (Ubuntu-based)
- **Size:** Optimized for production
- **Features:**
- Static file serving
- Gzip compression
- Security headers
### Server Image (`task-receipts-server`)
- **Base:** `node:18-slim` (Ubuntu-based)
- **Build:** Multi-stage build with Node.js 18 (Ubuntu-based)
- **Size:** Optimized for production
- **Features:**
- Non-root user execution
- Health checks
- Graceful shutdown handling
- Signal handling with dumb-init
## Development
### Building Individual Images
```bash
# Build client only
cd client
docker build -t task-receipts-client:dev .
# Build server only
cd server
docker build -t task-receipts-server:dev .
```
### Development with Docker Compose
Create a `docker-compose.dev.yml` for development:
```yaml
version: '3.8'
services:
server:
build:
context: ./server
dockerfile: Dockerfile
ports:
- "4000:4000"
environment:
- NODE_ENV=development
volumes:
- ./server/src:/app/src
- ./shared:/app/shared
command: npm run dev
client:
build:
context: ./client
dockerfile: Dockerfile
ports:
- "5173:80"
volumes:
- ./client/src:/app/src
- ./shared:/app/shared
```
## Configuration
### Environment Variables
The server supports the following environment variables:
- `NODE_ENV`: Environment (production/development)
- `PORT`: Server port (default: 4000)
### Database Persistence
The server's SQLite database is persisted in the `./server/data` directory when using Docker Compose.
## Troubleshooting
### Common Issues
1. **Port conflicts:**
- Ensure ports 80 and 4000 are available
- Modify ports in `docker-compose.yml` if needed
2. **Build failures:**
- Check Docker is running
- Ensure all source files are present
- Check network connectivity for npm packages
3. **Client can't connect to server:**
- Verify server is running and healthy
- Check CORS configuration
- Ensure proper network connectivity
### Logs
```bash
# View all logs
docker-compose logs
# View specific service logs
docker-compose logs server
docker-compose logs client
# Follow logs in real-time
docker-compose logs -f
```
### Health Checks
```bash
# Check service health
docker-compose ps
# Check individual container health
docker inspect task-receipts-server | grep Health -A 10
```
## Production Deployment
### Using Docker Compose
1. **Build and deploy:**
```bash
docker-compose -f docker-compose.yml up -d --build
```
2. **Update services:**
```bash
docker-compose pull
docker-compose up -d
```
### Using Individual Containers
1. **Build images:**
```bash
./build-docker.sh v1.0.0
```
2. **Deploy with orchestration:**
```bash
# Example with Docker Swarm or Kubernetes
docker stack deploy -c docker-compose.yml task-receipts
```
## Security Considerations
- Images run as non-root users
- Ubuntu-based images for better security and performance
- No sensitive data in images
- Health checks for monitoring
- Graceful shutdown handling
## Performance Optimization
- Multi-stage builds reduce image size
- Nginx for static file serving
- Ubuntu-based images for better performance
- Production-only dependencies in final images
- Layer caching optimization
## Monitoring
### Health Checks
- Server: GraphQL endpoint availability
- Client: Nginx process status
### Metrics
- Container resource usage
- Application logs
- Health check status
## Support
For issues with the Docker setup:
1. Check the troubleshooting section
2. Review container logs
3. Verify Docker and Docker Compose versions
4. Ensure all prerequisites are met

124
build-docker.sh Executable file
View File

@ -0,0 +1,124 @@
#!/bin/bash
# Build script for Task Receipts Docker images
# This script builds both the client and server Docker images
set -e # Exit on any error
# Configuration
PROJECT_NAME="task-receipts"
CLIENT_IMAGE_NAME="${PROJECT_NAME}-client"
SERVER_IMAGE_NAME="${PROJECT_NAME}-server"
VERSION=${1:-latest} # Use first argument as version, default to 'latest'
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if Docker is running
check_docker() {
if ! docker info > /dev/null 2>&1; then
print_error "Docker is not running or not accessible"
exit 1
fi
}
# Function to build client image
build_client() {
print_status "Building client image..."
# Build the image from root context with client Dockerfile
docker build -t "${CLIENT_IMAGE_NAME}:${VERSION}" -f client/Dockerfile .
if [ $? -eq 0 ]; then
print_success "Client image built successfully: ${CLIENT_IMAGE_NAME}:${VERSION}"
else
print_error "Failed to build client image"
exit 1
fi
}
# Function to build server image
build_server() {
print_status "Building server image..."
# Build the image from root context with server Dockerfile
docker build -t "${SERVER_IMAGE_NAME}:${VERSION}" -f server/Dockerfile .
if [ $? -eq 0 ]; then
print_success "Server image built successfully: ${SERVER_IMAGE_NAME}:${VERSION}"
else
print_error "Failed to build server image"
exit 1
fi
}
# Function to show usage
show_usage() {
echo "Usage: $0 [VERSION]"
echo ""
echo "Arguments:"
echo " VERSION Version tag for the Docker images (default: latest)"
echo ""
echo "Examples:"
echo " $0 # Build with 'latest' tag"
echo " $0 v1.0.0 # Build with 'v1.0.0' tag"
echo " $0 dev # Build with 'dev' tag"
}
# Main execution
main() {
print_status "Starting Docker build process for Task Receipts"
print_status "Version: ${VERSION}"
print_status "Client image: ${CLIENT_IMAGE_NAME}:${VERSION}"
print_status "Server image: ${SERVER_IMAGE_NAME}:${VERSION}"
echo ""
# Check if Docker is available
check_docker
# Build client image
build_client
# Build server image
build_server
echo ""
print_success "All images built successfully!"
echo ""
print_status "Built images:"
docker images | grep -E "(${CLIENT_IMAGE_NAME}|${SERVER_IMAGE_NAME})" || true
echo ""
print_status "To run the containers:"
echo " docker run -p 80:80 ${CLIENT_IMAGE_NAME}:${VERSION}"
echo " docker run -p 4000:4000 ${SERVER_IMAGE_NAME}:${VERSION}"
}
# Handle help argument
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
show_usage
exit 0
fi
# Run main function
main

44
client/.dockerignore Normal file
View File

@ -0,0 +1,44 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist/
build/
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Logs
logs/
*.log
# Coverage
coverage/
# Test outputs
test-output/
# Docker
Dockerfile
.dockerignore

33
client/Dockerfile Normal file
View File

@ -0,0 +1,33 @@
# Build stage
FROM node:18 AS builder
WORKDIR /app
# Copy root package files for workspaces
COPY package.json ./
COPY package-lock.json ./
# Copy client and shared code
COPY client/ ./client/
COPY shared/ ./shared/
# Install dependencies for client workspace
RUN npm ci --workspace=client
# Build the client
WORKDIR /app/client
RUN npm run build
# Production stage
FROM nginx:stable
COPY --from=builder /app/client/dist /usr/share/nginx/html
# Copy nginx configuration (optional - using default for now)
# COPY nginx.conf /etc/nginx/nginx.conf
# Expose port 80
EXPOSE 80
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

46
docker-compose.yml Normal file
View File

@ -0,0 +1,46 @@
version: '3.8'
services:
server:
build:
context: ./server
dockerfile: Dockerfile
container_name: task-receipts-server
ports:
- "4000:4000"
environment:
- NODE_ENV=production
volumes:
# Mount database directory for persistence
- ./server/data:/app/data
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:4000/graphql', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- task-receipts-network
client:
build:
context: ./client
dockerfile: Dockerfile
container_name: task-receipts-client
ports:
- "80:80"
depends_on:
server:
condition: service_healthy
restart: unless-stopped
networks:
- task-receipts-network
networks:
task-receipts-network:
driver: bridge
volumes:
server-data:
driver: local

48
server/.dockerignore Normal file
View File

@ -0,0 +1,48 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist/
build/
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Logs
logs/
*.log
# Coverage
coverage/
# Test outputs
test-output/
# Database files
*.sqlite3
*.db
# Docker
Dockerfile
.dockerignore

40
server/Dockerfile Normal file
View File

@ -0,0 +1,40 @@
# Build stage
FROM node:18 AS builder
WORKDIR /app
# Copy root package files for workspaces
COPY package.json ./
COPY package-lock.json ./
# Copy server and shared code
COPY server/ ./server/
COPY shared/ ./shared/
# Install dependencies for server workspace
RUN npm ci --workspace=server
# Build the server
WORKDIR /app/server
RUN npm run build
# Production stage
FROM node:18-slim
RUN apt-get update && apt-get install -y dumb-init && rm -rf /var/lib/apt/lists/*
RUN groupadd -r nodejs && useradd -r -g nodejs nodejs
WORKDIR /app/server
# Copy only server package.json and built code
COPY server/package.json ./
COPY --from=builder /app/server/dist ./dist
COPY --from=builder /app/shared ../shared
# Install only production dependencies
RUN npm install --omit=dev && npm cache clean --force
RUN chown -R nodejs:nodejs /app
USER nodejs
EXPOSE 4000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:4000/graphql', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/index.js"]

View File

@ -10,7 +10,8 @@
"baseUrl": ".",
"paths": {
"@shared/*": ["../shared/types/*"]
}
},
"outDir": "dist"
},
"include": ["src/**/*", "../shared/types/**/*"],
"exclude": ["node_modules", "dist"]