#!/bin/bash # ============================================================================= # deploy-project.sh - Automated Docker Deployment Script # ============================================================================= # This script automates the deployment of Docker containers. # It handles four scenarios: # - No .env, No container: Setup new instance with wizard # - Has .env, No container: Setup new instance using .env # - No .env, Has container: Renew instance with wizard # - Has .env, Has container: Renew instance using .env # # Uses /dev/tty for interactive prompts, enabling wizard to work even when # script is piped from curl. # # Builds Docker image locally from embedded Dockerfile (ephemeral runtime). # All configuration is stored in .env file. Docker volumes are used for # persistent storage without disturbing the host file system. # ============================================================================= set -e # --- Configuration --- # When script is piped from curl, BASH_SOURCE is unreliable, so we use PWD SCRIPT_DIR="${PWD}" ENV_FILE="${SCRIPT_DIR}/.env" DEFAULT_INSTANCE_NAME="my-app" IMAGE_NAME="my-app" IMAGE_TAG="latest" FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}" BUILD_CONTEXT_DIR="${SCRIPT_DIR}/.app-build" # --- Colors for output --- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # --- Helper Functions --- log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } # Check if .env file exists has_env_file() { [ -f "${ENV_FILE}" ] } # Check if container exists has_container() { docker ps -a --format '{{.Names}}' | grep -q "^${INSTANCE_NAME}$" 2>/dev/null || false } # Check if container is running is_container_running() { docker ps --format '{{.Names}}' | grep -q "^${INSTANCE_NAME}$" 2>/dev/null || false } # Load environment variables from .env file load_env_file() { if has_env_file; then set -a source "${ENV_FILE}" set +a log_info "Loaded configuration from .env file" fi } # Save environment variables to .env file save_env_file() { cat > "${ENV_FILE}" << EOF # Application Configuration # Generated by deploy-project.sh # Instance name INSTANCE_NAME="${INSTANCE_NAME}" # Docker image configuration (built locally) IMAGE_NAME="${IMAGE_NAME}" IMAGE_TAG="${IMAGE_TAG}" # Volume mappings (format: "VOLUME_NAME:CONTAINER_PATH") VOLUMES=( "_data-vol:/app/data" "_config-vol:/app/config" ) # Network configuration NETWORKS=( "_internal-net" ) # System settings DEBUG=false TZ="UTC" EOF log_success "Configuration saved to .env file" } # Interactive prompt - read from /dev/tty to work even when script is piped prompt_user() { local prompt_text="$1" local default_value="$2" local var_name="$3" echo -n "${prompt_text}" if [ -n "${default_value}" ]; then echo -n " [${default_value}]" fi echo -n ": " local user_input if [ -r /dev/tty ]; then read user_input "${BUILD_CONTEXT_DIR}/Dockerfile" << 'DOCKERFILE' # Base image FROM alpine:3.19 # Install any required dependencies RUN apk add --no-cache bash coreutils # Create application directory WORKDIR /app # Create data and config directories RUN mkdir -p /app/data /app/config # Keep container running indefinitely without starting any application # tail -f /dev/null runs forever, allowing users to exec in and run commands manually CMD ["tail", "-f", "/dev/null"] DOCKERFILE log_success "Build context created" } # Build Docker image locally build_image() { log_info "Building Docker image: ${FULL_IMAGE_NAME}" # Create build context create_build_context # Build the image docker build -t "${FULL_IMAGE_NAME}" "${BUILD_CONTEXT_DIR}" log_success "Docker image built successfully" } # Cleanup build context (ephemeral) cleanup_build_context() { if [ -d "${BUILD_CONTEXT_DIR}" ]; then rm -rf "${BUILD_CONTEXT_DIR}" log_info "Cleaned up ephemeral build context" fi } # Build Docker run arguments for volumes build_volume_args() { local args="" for vol in "${VOLUMES[@]}"; do local src=$(echo "$vol" | cut -d: -f1) local dst=$(echo "$vol" | cut -d: -f2) # Handle named volumes (start with _) if [[ "$src" == _* ]]; then src="${INSTANCE_NAME}${src}" # Create volume if not exists docker volume inspect "${src}" &>/dev/null || \ docker volume create "${src}" >/dev/null fi args="$args -v ${src}:${dst}" done echo "$args" } # Build Docker run arguments for networks build_network_args() { local args="" for net in "${NETWORKS[@]}"; do # Handle named networks (start with _) if [[ "$net" == _* ]]; then net="${INSTANCE_NAME}${net}" # Create network if not exists docker network inspect "${net}" &>/dev/null || \ docker network create "${net}" >/dev/null fi args="$args --network ${net}" done echo "$args" } # Remove existing container remove_container() { if has_container; then log_info "Stopping existing container..." docker stop "${INSTANCE_NAME}" 2>/dev/null || true log_info "Removing existing container..." docker rm "${INSTANCE_NAME}" 2>/dev/null || true log_success "Existing container removed" fi } # Deploy container deploy_container() { log_info "Deploying container '${INSTANCE_NAME}'..." # Build image locally (ephemeral runtime policy) build_image # Remove existing container if present remove_container # Build volume and network arguments local volume_args=$(build_volume_args) local network_args=$(build_network_args) # Create and start container docker run -d \ --name "${INSTANCE_NAME}" \ --restart unless-stopped \ ${volume_args} \ ${network_args} \ -e TZ="${TZ:-UTC}" \ "${FULL_IMAGE_NAME}" # Cleanup ephemeral build context cleanup_build_context log_success "Container '${INSTANCE_NAME}' deployed successfully" } # Show deployment complete output show_deployment_complete() { echo "" echo "============================================" echo " Deployment Complete! Container Operation Commands" echo "" echo " Re-Run Container:" echo " - Prepare your \`.env\`" echo " - curl -s \"/deploy-project.sh\" | bash" echo " ------------------------------------------------------------" echo " Container Accessing:" echo " docker exec -it -u root ${INSTANCE_NAME} bash" echo " docker exec -it -u node ${INSTANCE_NAME} bash" echo " ------------------------------------------------------------" echo " View the Logs: docker logs --tail 50 ${INSTANCE_NAME}" echo " ------------------------------------------------------------" echo " Inspect: docker inspect ${INSTANCE_NAME}" echo " ------------------------------------------------------------" echo " Remove Container: docker rm -f ${INSTANCE_NAME}" echo " ------------------------------------------------------------" echo " Remove Volumes (if needed):" # List volumes associated with this instance local volumes="" for vol in "${VOLUMES[@]}"; do local src=$(echo "$vol" | cut -d: -f1) if [[ "$src" == _* ]]; then src="${INSTANCE_NAME}${src}" fi volumes="${volumes} ${src}" done if [ -n "${volumes}" ]; then echo " docker volume rm${volumes}" fi echo "============================================" } # Validate deployment validate_deployment() { log_info "Validating deployment..." sleep 2 if is_container_running; then log_success "Container is running" # Show deployment complete output with commands show_deployment_complete return 0 else log_error "Container failed to start" log_info "Check logs with: docker logs ${INSTANCE_NAME}" return 1 fi } # Display usage information usage() { cat << EOF Usage: curl -s "/deploy-project.sh" | bash Automated Docker deployment script with ephemeral runtime policy. The script automatically detects your setup and: - First run (no .env): Starts interactive wizard - With .env: Uses existing configuration - Container exists: Recreates with current settings Key Features: - Builds Docker image locally from embedded Dockerfile - Ephemeral build context (cleaned up after build) - Container runs with 'tail -f /dev/null' for manual exec - Persistent data stored in Docker volumes Environment Variables (in .env file): INSTANCE_NAME Name of the Docker container IMAGE_NAME Docker image name (built locally) IMAGE_TAG Image version tag VOLUMES Volume mappings (array) NETWORKS Network connections (array) DEBUG Enable verbose logging Examples: # First run - will trigger interactive wizard curl -s "/deploy-project.sh" | bash # With existing .env file curl -s "/deploy-project.sh" | bash # Access the running container docker exec -it bash Note: Configuration is stored in .env file in the current directory. The Docker image is built locally and build context is cleaned up immediately. EOF } # Main execution main() { # Check for help options if [ "$1" = "-h" ] || [ "$1" = "--help" ] || [ "$1" = "help" ]; then usage exit 0 fi # Load existing configuration load_env_file # Determine action based on state local has_env=$(has_env_file && echo "yes" || echo "no") local has_ctn=$(has_container && echo "yes" || echo "no") log_info "Configuration: .env=${has_env}, Container=${has_ctn}" if [ "$has_env" = "no" ]; then # No .env file - run wizard log_info "No configuration found. Starting interactive setup..." interactive_wizard save_env_file deploy_container else # Has .env file - use existing config log_info "Using existing configuration..." # Validate required variables if [ -z "${INSTANCE_NAME}" ]; then log_error "INSTANCE_NAME not set in .env" exit 1 fi # Rebuild image name if components changed if [ -n "${IMAGE_NAME}" ]; then FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG:-latest}" fi deploy_container fi # Validate deployment validate_deployment log_success "Deployment complete!" } # Run main function main "$@"