Add Docker Compose configuration and environment files for local and production setups

- Created docker-compose.docker-local.yml for local testing of frontend and backend services.
- Added .env.development for development environment configuration.
- Introduced .env.docker-local for local Docker environment settings.
- Added .env.production for production environment configuration for Synology deployment.
This commit is contained in:
2026-01-30 11:29:17 -05:00
parent 4d14f9ba9c
commit fee8fe2551
30 changed files with 899 additions and 304 deletions

View File

@@ -10,18 +10,21 @@
# ./deploy.sh [options]
#
# Options:
# --target <internal|external|local> Deployment target (default: internal)
# --port <number> Frontend HTTP port (default: 8080)
# --ssl-port <number> Frontend HTTPS port (optional)
# --backend-port <number> Backend port (default: 3000)
# --skip-tests Skip test execution
# --skip-build Skip build step (use existing dist/)
# --no-backup Skip backup creation
# --dry-run Show what would be deployed without deploying
# --target <local|production> Deployment target (default: local)
# --port <number> Frontend HTTP port (default: 8099)
# --backend-port <number> Backend port (default: 3099)
# --skip-tests Skip test execution
# --skip-build Skip build step (use existing dist/)
# --no-backup Skip backup creation
# --dry-run Show what would be deployed without deploying
#
# Deployment Strategies:
# local - Docker deployment on localhost:8099 for production testing
# production - Deploy to Synology at https://app.pokedex.online
#
# Examples:
# ./deploy.sh --target internal
# ./deploy.sh --target external --port 8080 --backend-port 3000
# ./deploy.sh --target local
# ./deploy.sh --target production
# ./deploy.sh --dry-run
###############################################################################
@@ -35,9 +38,8 @@ BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default configuration
TARGET="internal"
TARGET="local"
PORT=8099
SSL_PORT=""
BACKEND_PORT=3099
SKIP_TESTS=false
SKIP_BUILD=false
@@ -89,10 +91,6 @@ parse_args() {
PORT="$2"
shift 2
;;
--ssl-port)
SSL_PORT="$2"
shift 2
;;
--backend-port)
BACKEND_PORT="$2"
shift 2
@@ -166,21 +164,32 @@ check_prerequisites() {
check_environment() {
log_step "🔧 Checking Environment Configuration"
# Check if .env exists
if [ ! -f "$PROJECT_ROOT/server/.env" ]; then
log_warning ".env file not found in server/"
log_info "Copy server/.env.example to server/.env and configure it"
if [ "$DRY_RUN" = false ]; then
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
else
log_success "Environment configuration found"
# Validate target
if [ "$TARGET" != "local" ] && [ "$TARGET" != "production" ]; then
log_error "Invalid target: $TARGET"
log_info "Valid targets: local, production"
exit 1
fi
# Check for mode-specific env files
local mode
if [ "$TARGET" = "local" ]; then
mode="docker-local"
else
mode="production"
fi
if [ ! -f "$PROJECT_ROOT/.env.$mode" ]; then
log_error "Environment file .env.$mode not found"
exit 1
fi
log_success "Frontend environment configuration found (.env.$mode)"
if [ ! -f "$PROJECT_ROOT/server/.env.$mode" ]; then
log_error "Environment file server/.env.$mode not found"
exit 1
fi
log_success "Backend environment configuration found (server/.env.$mode)"
}
run_tests() {
@@ -209,39 +218,17 @@ run_tests() {
# Build
###############################################################################
prepare_env_for_build() {
# Create/update .env.local with correct Discord redirect URI for the target
local env_file="$PROJECT_ROOT/.env.local"
local redirect_uri
prepare_build_mode() {
# Determine Vite build mode based on deployment target
if [ "$TARGET" = "local" ]; then
BUILD_MODE="docker-local"
else
BUILD_MODE="production"
fi
# Determine correct redirect URI based on target and ports
case "$TARGET" in
local)
# For Docker, use the frontend port
redirect_uri="http://localhost:${PORT}/oauth/callback"
;;
internal|external)
# For dev server, use default Vite port
redirect_uri="http://localhost:5173/oauth/callback"
;;
*)
redirect_uri="http://localhost:5173/oauth/callback"
;;
esac
log_info "Preparing environment for ${TARGET} deployment..."
log_info "Discord redirect URI: ${redirect_uri}"
# Create/update .env.local with Discord credentials
cat > "$env_file" << EOF
# Generated by deploy.sh for ${TARGET} deployment
# These variables are embedded into the frontend bundle at build time
VITE_DISCORD_CLIENT_ID=1466544972059775223
VITE_DISCORD_REDIRECT_URI=${redirect_uri}
EOF
log_info ".env.local created with correct redirect URI"
log_info "Preparing build for ${TARGET} deployment..."
log_info "Vite build mode: ${BUILD_MODE}"
log_info "Environment file: .env.${BUILD_MODE}"
}
build_application() {
@@ -259,18 +246,18 @@ build_application() {
log_step "🔨 Building Application"
# Prepare environment before building
prepare_env_for_build
# Prepare build mode
prepare_build_mode
log_info "Building frontend..."
npm run build:frontend || {
log_info "Building frontend with mode: $BUILD_MODE..."
npx vite build --mode "$BUILD_MODE" || {
log_error "Frontend build failed"
exit 1
}
log_success "Frontend built successfully"
log_info "Verifying build..."
npm run build:verify || {
BUILD_TARGET="$TARGET" npm run build:verify || {
log_error "Build verification failed"
exit 1
}
@@ -327,26 +314,162 @@ deploy_to_server() {
log_info "DRY RUN - Would deploy with following configuration:"
log_info " Target: $TARGET"
log_info " Frontend Port: $PORT"
[ -n "$SSL_PORT" ] && log_info " SSL Port: $SSL_PORT"
log_info " Backend Port: $BACKEND_PORT"
log_success "Dry run completed"
return
fi
# Build deployment command
DEPLOY_CMD="node ../../utils/deploy-pokedex.js --target $TARGET --port $PORT --backend-port $BACKEND_PORT"
[ -n "$SSL_PORT" ] && DEPLOY_CMD="$DEPLOY_CMD --ssl-port $SSL_PORT"
if [ "$TARGET" = "local" ]; then
log_info "Deploying to local Docker..."
# Copy server env file
cp "$PROJECT_ROOT/server/.env.docker-local" "$PROJECT_ROOT/server/.env" || {
log_error "Failed to copy server environment file"
exit 1
}
# Use docker-compose.docker-local.yml
log_info "Starting Docker containers..."
docker compose -f docker-compose.docker-local.yml down || true
docker compose -f docker-compose.docker-local.yml up -d --build || {
log_error "Docker deployment failed"
exit 1
}
else
log_info "Deploying to production (Synology)..."
deploy_to_synology
fi
log_info "Executing deployment..."
log_info "Command: $DEPLOY_CMD"
log_success "Deployment completed successfully"
}
deploy_to_synology() {
# SSH Configuration
local SSH_HOST="10.0.0.81"
local SSH_PORT="2323"
local SSH_USER="GregRJacobs"
local SSH_KEY="$HOME/.ssh/ds3627xs_gregrjacobs"
local REMOTE_PATH="/volume1/docker/pokedex-online"
eval "$DEPLOY_CMD" || {
log_error "Deployment failed"
log_info "Check logs above for details"
log_info "SSH Config: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}"
# Verify SSH key exists
if [ ! -f "$SSH_KEY" ]; then
log_error "SSH key not found: $SSH_KEY"
exit 1
fi
# Test SSH connection
log_info "Testing SSH connection..."
if ! ssh -i "$SSH_KEY" -p "$SSH_PORT" -o ConnectTimeout=10 -o BatchMode=yes "$SSH_USER@$SSH_HOST" "echo 'Connection successful'" > /dev/null 2>&1; then
log_error "SSH connection failed"
log_info "Please verify:"
log_info " 1. SSH key exists: $SSH_KEY"
log_info " 2. Key has correct permissions: chmod 600 $SSH_KEY"
log_info " 3. Public key is in authorized_keys on server"
log_info " 4. You can connect manually: ssh -i $SSH_KEY -p $SSH_PORT $SSH_USER@$SSH_HOST"
exit 1
fi
log_success "SSH connection verified"
# Create remote directory
log_info "Creating remote directory..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "mkdir -p $REMOTE_PATH/dist $REMOTE_PATH/server" || {
log_error "Failed to create remote directory"
exit 1
}
log_success "Deployment completed successfully"
# Transfer dist directory using tar over SSH
log_info "Transferring frontend build (dist/)..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/dist && mkdir -p $REMOTE_PATH/dist"
tar -czf - -C "$PROJECT_ROOT" dist 2>/dev/null | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || {
log_error "Failed to transfer dist directory"
exit 1
}
log_success "Frontend build transferred"
# Transfer server directory (excluding node_modules) using tar over SSH
log_info "Transferring backend code (server/)..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "rm -rf $REMOTE_PATH/server && mkdir -p $REMOTE_PATH/server"
tar -czf - -C "$PROJECT_ROOT" \
--exclude='node_modules' \
--exclude='.env*' \
--exclude='data' \
--exclude='logs' \
server 2>/dev/null | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"tar -xzf - -C $REMOTE_PATH --strip-components=0 2>/dev/null" || {
log_error "Failed to transfer server directory"
exit 1
}
log_success "Backend code transferred"
# Copy production environment file to server (after server code transfer)
log_info "Copying server environment file..."
cat "$PROJECT_ROOT/server/.env.production" | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cat > $REMOTE_PATH/server/.env" || {
log_error "Failed to copy server .env file"
exit 1
}
log_success "Server environment configured"
# Create required directories for volume mounts
log_info "Creating volume mount directories..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"mkdir -p $REMOTE_PATH/server/data $REMOTE_PATH/server/logs" || {
log_error "Failed to create volume mount directories"
exit 1
}
# Transfer Docker configuration files
log_info "Transferring Docker configuration..."
for file in docker-compose.production.yml nginx.conf Dockerfile.frontend; do
cat "$PROJECT_ROOT/$file" | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cat > $REMOTE_PATH/$file" || {
log_error "Failed to transfer $file"
exit 1
}
done
cat "$PROJECT_ROOT/server/Dockerfile" | \
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cat > $REMOTE_PATH/server/Dockerfile" || {
log_error "Failed to transfer server Dockerfile"
exit 1
}
log_success "Docker configuration transferred"
# Find Docker on remote system
log_info "Locating Docker on remote system..."
DOCKER_PATH=$(ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"which docker 2>/dev/null || echo /usr/local/bin/docker")
log_info "Using Docker at: $DOCKER_PATH"
# Stop and remove existing containers (force cleanup)
log_info "Stopping and removing existing containers..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans 2>/dev/null || true"
# Force remove any lingering containers with matching names
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"$DOCKER_PATH rm -f pokedex-frontend pokedex-backend 2>/dev/null || true"
# Start containers
log_info "Starting Docker containers..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml up -d --build" || {
log_error "Failed to start Docker containers"
log_info "Rolling back..."
ssh -i "$SSH_KEY" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" \
"cd $REMOTE_PATH && $DOCKER_PATH compose -f docker-compose.production.yml down --remove-orphans"
exit 1
}
log_success "Containers started"
}
###############################################################################
@@ -362,10 +485,8 @@ verify_deployment() {
fi
# Determine host based on target
if [ "$TARGET" = "internal" ]; then
if [ "$TARGET" = "production" ]; then
HOST="10.0.0.81"
elif [ "$TARGET" = "external" ]; then
HOST="home.gregrjacobs.com"
else
HOST="localhost"
fi
@@ -395,32 +516,32 @@ print_summary() {
return
fi
# Determine host based on target
if [ "$TARGET" = "internal" ]; then
HOST="10.0.0.81"
elif [ "$TARGET" = "external" ]; then
HOST="home.gregrjacobs.com"
# Determine URLs based on target
if [ "$TARGET" = "production" ]; then
FRONTEND_URL="https://app.pokedex.online"
BACKEND_URL="http://10.0.0.81:$BACKEND_PORT"
else
HOST="localhost"
FRONTEND_URL="http://localhost:$PORT"
BACKEND_URL="http://localhost:$BACKEND_PORT"
fi
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} 🎉 Deployment Successful!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e " ${BLUE}Frontend:${NC} http://$HOST:$PORT"
[ -n "$SSL_PORT" ] && echo -e " ${BLUE}HTTPS:${NC} https://$HOST:$SSL_PORT"
echo -e " ${BLUE}Backend:${NC} http://$HOST:$BACKEND_PORT"
echo -e " ${BLUE}Frontend:${NC} $FRONTEND_URL"
echo -e " ${BLUE}Backend:${NC} $BACKEND_URL"
echo -e " ${BLUE}Target:${NC} $TARGET"
echo ""
echo -e " ${YELLOW}Next Steps:${NC}"
echo -e " • Test the application manually"
if [ "$TARGET" = "local" ]; then
echo -e " • Check logs: docker compose -f docker-compose.tmp.yml logs -f"
echo -e " • Check logs: docker compose -f docker-compose.local.yml logs -f"
echo -e " • Stop containers: docker compose -f docker-compose.local.yml down"
else
echo -e " • Check logs: npm run docker:logs"
echo -e " • Check logs via SSH or Docker commands on Synology"
fi
echo -e " • Monitor backend: curl http://$HOST:$BACKEND_PORT/health"
echo -e " • Monitor backend: curl $BACKEND_URL/health"
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}