feat: Add Docker containerization for consistent deployment
- Multi-stage Dockerfile with Node.js 20 Alpine base - Production and development docker-compose configurations - Health check API endpoint for container monitoring - Build and deployment scripts with versioning support - Port 3000 configuration for nginx compatibility - Non-root user and security hardening - Resource limits and logging configuration - Package.json scripts for Docker operations This eliminates dependency conflicts and provides reproducible deployments. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
81
.dockerignore
Normal file
81
.dockerignore
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
.npm
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Development files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Build outputs (will be created during Docker build)
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs (will be mounted as volume)
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Test files
|
||||||
|
coverage/
|
||||||
|
*.lcov
|
||||||
|
.nyc_output/
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.eslintcache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
docs/
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
|
!CLAUDE.md
|
||||||
|
|
||||||
|
# Docker files (prevent recursion)
|
||||||
|
Dockerfile*
|
||||||
|
docker-compose*.yml
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Development scripts
|
||||||
|
scripts/
|
||||||
|
test-*.js
|
||||||
|
test-*.mjs
|
||||||
|
|
||||||
|
# Backup files
|
||||||
|
*.backup
|
||||||
|
*_backup.*
|
||||||
|
|
||||||
|
# Style guide and design files
|
||||||
|
styleGuide/
|
||||||
|
*.pdf
|
||||||
|
*.jpg
|
||||||
|
*.png
|
||||||
|
cookies.txt
|
||||||
61
Dockerfile
Normal file
61
Dockerfile
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Multi-stage build for Black Canyon Tickets
|
||||||
|
# Stage 1: Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install all dependencies (including dev dependencies for build)
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: Production stage
|
||||||
|
FROM node:20-alpine AS production
|
||||||
|
|
||||||
|
# Install security updates
|
||||||
|
RUN apk update && apk upgrade && apk add --no-cache dumb-init
|
||||||
|
|
||||||
|
# Create app user for security
|
||||||
|
RUN addgroup -g 1001 -S nodejs
|
||||||
|
RUN adduser -S astro -u 1001
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
RUN npm ci --only=production && npm cache clean --force
|
||||||
|
|
||||||
|
# Copy built application from builder stage
|
||||||
|
COPY --from=builder --chown=astro:nodejs /app/dist ./dist
|
||||||
|
|
||||||
|
# Copy additional necessary files
|
||||||
|
COPY --chown=astro:nodejs setup-schema.js ./
|
||||||
|
COPY --chown=astro:nodejs setup-super-admins.js ./
|
||||||
|
|
||||||
|
# Create logs directory
|
||||||
|
RUN mkdir -p logs && chown astro:nodejs logs
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER astro
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD node -e "const http=require('http');const options={hostname:'localhost',port:3000,path:'/api/health',timeout:2000};const req=http.request(options,(res)=>{process.exit(res.statusCode===200?0:1)});req.on('error',()=>{process.exit(1)});req.end();" || exit 1
|
||||||
|
|
||||||
|
# Start the application with dumb-init for proper signal handling
|
||||||
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
|
CMD ["node", "./dist/server/entry.mjs"]
|
||||||
58
docker-compose.prod.yml
Normal file
58
docker-compose.prod.yml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bct-app:
|
||||||
|
image: bct-whitelabel:latest
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- PORT=3000
|
||||||
|
# Supabase
|
||||||
|
- PUBLIC_SUPABASE_URL=${PUBLIC_SUPABASE_URL}
|
||||||
|
- PUBLIC_SUPABASE_ANON_KEY=${PUBLIC_SUPABASE_ANON_KEY}
|
||||||
|
- SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
|
||||||
|
# Stripe
|
||||||
|
- STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY}
|
||||||
|
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||||||
|
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
|
||||||
|
# Email
|
||||||
|
- RESEND_API_KEY=${RESEND_API_KEY}
|
||||||
|
# Monitoring
|
||||||
|
- SENTRY_DSN=${SENTRY_DSN}
|
||||||
|
- SENTRY_RELEASE=${SENTRY_RELEASE:-latest}
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "const http=require('http');const options={hostname:'localhost',port:3000,path:'/api/health',timeout:2000};const req=http.request(options,(res)=>{process.exit(res.statusCode===200?0:1)});req.on('error',()=>{process.exit(1)});req.end();"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- bct-network
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 1G
|
||||||
|
cpus: '1.0'
|
||||||
|
reservations:
|
||||||
|
memory: 512M
|
||||||
|
cpus: '0.5'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bct-network:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.20.0.0/16
|
||||||
42
docker-compose.yml
Normal file
42
docker-compose.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bct-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: production
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- PORT=3000
|
||||||
|
# Supabase
|
||||||
|
- PUBLIC_SUPABASE_URL=${PUBLIC_SUPABASE_URL}
|
||||||
|
- PUBLIC_SUPABASE_ANON_KEY=${PUBLIC_SUPABASE_ANON_KEY}
|
||||||
|
- SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
|
||||||
|
# Stripe
|
||||||
|
- STRIPE_PUBLISHABLE_KEY=${STRIPE_PUBLISHABLE_KEY}
|
||||||
|
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}
|
||||||
|
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
|
||||||
|
# Email
|
||||||
|
- RESEND_API_KEY=${RESEND_API_KEY}
|
||||||
|
# Monitoring
|
||||||
|
- SENTRY_DSN=${SENTRY_DSN}
|
||||||
|
- SENTRY_RELEASE=${SENTRY_RELEASE:-unknown}
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "node", "-e", "const http=require('http');const options={hostname:'localhost',port:3000,path:'/api/health',timeout:2000};const req=http.request(options,(res)=>{process.exit(res.statusCode===200?0:1)});req.on('error',()=>{process.exit(1)});req.end();"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 40s
|
||||||
|
networks:
|
||||||
|
- bct-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
bct-network:
|
||||||
|
driver: bridge
|
||||||
10
package.json
10
package.json
@@ -13,7 +13,15 @@
|
|||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lint:fix": "eslint . --fix",
|
"lint:fix": "eslint . --fix",
|
||||||
"mcp:stripe": "npx @stripe/mcp --tools=all --api-key=$STRIPE_SECRET_KEY",
|
"mcp:stripe": "npx @stripe/mcp --tools=all --api-key=$STRIPE_SECRET_KEY",
|
||||||
"mcp:stripe:debug": "npx @modelcontextprotocol/inspector npx @stripe/mcp --tools=all --api-key=$STRIPE_SECRET_KEY"
|
"mcp:stripe:debug": "npx @modelcontextprotocol/inspector npx @stripe/mcp --tools=all --api-key=$STRIPE_SECRET_KEY",
|
||||||
|
"docker:build": "./scripts/docker-build.sh",
|
||||||
|
"docker:build:version": "./scripts/docker-build.sh",
|
||||||
|
"docker:deploy": "./scripts/docker-deploy.sh",
|
||||||
|
"docker:up": "docker-compose up -d",
|
||||||
|
"docker:down": "docker-compose down",
|
||||||
|
"docker:logs": "docker-compose logs -f",
|
||||||
|
"docker:prod:up": "docker-compose -f docker-compose.prod.yml up -d",
|
||||||
|
"docker:prod:down": "docker-compose -f docker-compose.prod.yml down"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
|
|||||||
60
scripts/docker-build.sh
Executable file
60
scripts/docker-build.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Docker build script for Black Canyon Tickets
|
||||||
|
# Usage: ./scripts/docker-build.sh [version]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
IMAGE_NAME="bct-whitelabel"
|
||||||
|
REGISTRY="${DOCKER_REGISTRY:-}" # Optional registry prefix
|
||||||
|
VERSION="${1:-latest}"
|
||||||
|
PLATFORM="${PLATFORM:-linux/amd64}"
|
||||||
|
|
||||||
|
echo -e "${GREEN}🐳 Building Docker image for Black Canyon Tickets${NC}"
|
||||||
|
echo -e "${YELLOW}Image: ${IMAGE_NAME}:${VERSION}${NC}"
|
||||||
|
echo -e "${YELLOW}Platform: ${PLATFORM}${NC}"
|
||||||
|
|
||||||
|
# Build the image
|
||||||
|
echo -e "${GREEN}📦 Building Docker image...${NC}"
|
||||||
|
if [ -n "$REGISTRY" ]; then
|
||||||
|
FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}"
|
||||||
|
else
|
||||||
|
FULL_IMAGE_NAME="${IMAGE_NAME}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
--platform "$PLATFORM" \
|
||||||
|
--tag "${FULL_IMAGE_NAME}:${VERSION}" \
|
||||||
|
--tag "${FULL_IMAGE_NAME}:latest" \
|
||||||
|
--build-arg BUILD_DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
||||||
|
--build-arg VCS_REF="$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" \
|
||||||
|
--build-arg VERSION="$VERSION" \
|
||||||
|
.
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Docker image built successfully!${NC}"
|
||||||
|
echo -e "${YELLOW}Image tags:${NC}"
|
||||||
|
echo -e " - ${FULL_IMAGE_NAME}:${VERSION}"
|
||||||
|
echo -e " - ${FULL_IMAGE_NAME}:latest"
|
||||||
|
|
||||||
|
# Optional: Push to registry
|
||||||
|
if [ "$2" = "--push" ] && [ -n "$REGISTRY" ]; then
|
||||||
|
echo -e "${GREEN}🚀 Pushing to registry...${NC}"
|
||||||
|
docker push "${FULL_IMAGE_NAME}:${VERSION}"
|
||||||
|
docker push "${FULL_IMAGE_NAME}:latest"
|
||||||
|
echo -e "${GREEN}✅ Images pushed to registry!${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show image size
|
||||||
|
echo -e "${GREEN}📊 Image information:${NC}"
|
||||||
|
docker images "${FULL_IMAGE_NAME}" --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}"
|
||||||
|
|
||||||
|
echo -e "${GREEN}🎉 Build complete!${NC}"
|
||||||
|
echo -e "${YELLOW}To run locally: docker run -p 3000:3000 ${FULL_IMAGE_NAME}:${VERSION}${NC}"
|
||||||
|
echo -e "${YELLOW}Or use: docker-compose up${NC}"
|
||||||
178
scripts/docker-deploy.sh
Executable file
178
scripts/docker-deploy.sh
Executable file
@@ -0,0 +1,178 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Docker deployment script for Black Canyon Tickets
|
||||||
|
# Usage: ./scripts/docker-deploy.sh [environment]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
ENVIRONMENT="${1:-production}"
|
||||||
|
IMAGE_NAME="bct-whitelabel"
|
||||||
|
VERSION="${2:-latest}"
|
||||||
|
COMPOSE_FILE="docker-compose.prod.yml"
|
||||||
|
|
||||||
|
echo -e "${GREEN}🚀 Deploying Black Canyon Tickets${NC}"
|
||||||
|
echo -e "${YELLOW}Environment: ${ENVIRONMENT}${NC}"
|
||||||
|
echo -e "${YELLOW}Version: ${VERSION}${NC}"
|
||||||
|
|
||||||
|
# Function to check if required environment variables are set
|
||||||
|
check_env_vars() {
|
||||||
|
local required_vars=(
|
||||||
|
"PUBLIC_SUPABASE_URL"
|
||||||
|
"PUBLIC_SUPABASE_ANON_KEY"
|
||||||
|
"SUPABASE_SERVICE_ROLE_KEY"
|
||||||
|
"STRIPE_PUBLISHABLE_KEY"
|
||||||
|
"STRIPE_SECRET_KEY"
|
||||||
|
"STRIPE_WEBHOOK_SECRET"
|
||||||
|
"RESEND_API_KEY"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo -e "${BLUE}🔍 Checking environment variables...${NC}"
|
||||||
|
local missing_vars=()
|
||||||
|
|
||||||
|
for var in "${required_vars[@]}"; do
|
||||||
|
if [ -z "${!var}" ]; then
|
||||||
|
missing_vars+=("$var")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#missing_vars[@]} -ne 0 ]; then
|
||||||
|
echo -e "${RED}❌ Missing required environment variables:${NC}"
|
||||||
|
for var in "${missing_vars[@]}"; do
|
||||||
|
echo -e " - $var"
|
||||||
|
done
|
||||||
|
echo -e "${YELLOW}💡 Please set these variables in your .env file or environment${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ All required environment variables are set${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if .env file exists
|
||||||
|
check_env_file() {
|
||||||
|
if [ ! -f ".env" ]; then
|
||||||
|
echo -e "${YELLOW}⚠️ No .env file found. Creating from environment variables...${NC}"
|
||||||
|
# You might want to create a template .env file here
|
||||||
|
echo -e "${YELLOW}💡 Please ensure all required environment variables are available${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${GREEN}✅ .env file found${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to pull latest image
|
||||||
|
pull_image() {
|
||||||
|
echo -e "${BLUE}📥 Pulling latest image...${NC}"
|
||||||
|
if ! docker pull "${IMAGE_NAME}:${VERSION}" 2>/dev/null; then
|
||||||
|
echo -e "${YELLOW}⚠️ Could not pull image from registry. Using local image.${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to stop existing containers
|
||||||
|
stop_containers() {
|
||||||
|
echo -e "${BLUE}🛑 Stopping existing containers...${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" down --remove-orphans || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to start containers
|
||||||
|
start_containers() {
|
||||||
|
echo -e "${BLUE}🔄 Starting containers...${NC}"
|
||||||
|
|
||||||
|
# Set the image version
|
||||||
|
export DOCKER_IMAGE_TAG="$VERSION"
|
||||||
|
|
||||||
|
# Start services
|
||||||
|
docker-compose -f "$COMPOSE_FILE" up -d
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Containers started!${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to wait for health check
|
||||||
|
wait_for_health() {
|
||||||
|
echo -e "${BLUE}🏥 Waiting for application to be healthy...${NC}"
|
||||||
|
local max_attempts=30
|
||||||
|
local attempt=1
|
||||||
|
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
if docker-compose -f "$COMPOSE_FILE" ps --services --filter "status=running" | grep -q "bct-app"; then
|
||||||
|
if docker-compose -f "$COMPOSE_FILE" exec -T bct-app node -e "const http=require('http');const options={hostname:'localhost',port:4321,path:'/api/health',timeout:2000};const req=http.request(options,(res)=>{process.exit(res.statusCode===200?0:1)});req.on('error',()=>{process.exit(1)});req.end();" 2>/dev/null; then
|
||||||
|
echo -e "${GREEN}✅ Application is healthy!${NC}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}⏳ Waiting... (attempt $attempt/$max_attempts)${NC}"
|
||||||
|
sleep 10
|
||||||
|
((attempt++))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${RED}❌ Application failed to become healthy${NC}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to show deployment status
|
||||||
|
show_status() {
|
||||||
|
echo -e "${GREEN}📊 Deployment Status:${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" ps
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}📋 Container Logs (last 20 lines):${NC}"
|
||||||
|
docker-compose -f "$COMPOSE_FILE" logs --tail=20 bct-app
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}🌐 Application should be available at:${NC}"
|
||||||
|
echo -e "${BLUE} http://localhost:3000${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to cleanup old images
|
||||||
|
cleanup() {
|
||||||
|
echo -e "${BLUE}🧹 Cleaning up old images...${NC}"
|
||||||
|
docker image prune -f || true
|
||||||
|
echo -e "${GREEN}✅ Cleanup complete${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main deployment process
|
||||||
|
main() {
|
||||||
|
echo -e "${GREEN}Starting deployment process...${NC}"
|
||||||
|
|
||||||
|
# Pre-deployment checks
|
||||||
|
check_env_file
|
||||||
|
check_env_vars
|
||||||
|
|
||||||
|
# Deployment steps
|
||||||
|
pull_image
|
||||||
|
stop_containers
|
||||||
|
start_containers
|
||||||
|
|
||||||
|
# Post-deployment verification
|
||||||
|
if wait_for_health; then
|
||||||
|
show_status
|
||||||
|
cleanup
|
||||||
|
echo -e "${GREEN}🎉 Deployment completed successfully!${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}💥 Deployment failed!${NC}"
|
||||||
|
echo -e "${YELLOW}📋 Check logs with: docker-compose -f $COMPOSE_FILE logs${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle script arguments
|
||||||
|
case "${1:-}" in
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: $0 [environment] [version]"
|
||||||
|
echo " environment: production (default)"
|
||||||
|
echo " version: Docker image tag (default: latest)"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " $0 # Deploy latest to production"
|
||||||
|
echo " $0 production v1.2.3 # Deploy specific version"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
main
|
||||||
|
;;
|
||||||
|
esac
|
||||||
34
src/pages/api/health.ts
Normal file
34
src/pages/api/health.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
|
export const GET: APIRoute = async () => {
|
||||||
|
try {
|
||||||
|
// Basic health check - could be extended to check database connectivity
|
||||||
|
const healthStatus = {
|
||||||
|
status: 'healthy',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
environment: process.env.NODE_ENV || 'development',
|
||||||
|
uptime: process.uptime()
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(healthStatus), {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
status: 'unhealthy',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
}), {
|
||||||
|
status: 503,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user