Add Docker support for client and server with Ubuntu-based images, build script, docker-compose, and comprehensive documentation
This commit is contained in:
parent
02d4bae997
commit
599ed993d3
253
DOCKER.md
Normal file
253
DOCKER.md
Normal 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
124
build-docker.sh
Executable 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
44
client/.dockerignore
Normal 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
33
client/Dockerfile
Normal 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
46
docker-compose.yml
Normal 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
48
server/.dockerignore
Normal 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
40
server/Dockerfile
Normal 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"]
|
@ -10,7 +10,8 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@shared/*": ["../shared/types/*"]
|
"@shared/*": ["../shared/types/*"]
|
||||||
}
|
},
|
||||||
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "../shared/types/**/*"],
|
"include": ["src/**/*", "../shared/types/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
|
Loading…
Reference in New Issue
Block a user