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": ".",
|
||||
"paths": {
|
||||
"@shared/*": ["../shared/types/*"]
|
||||
}
|
||||
},
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src/**/*", "../shared/types/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
Loading…
Reference in New Issue
Block a user