name: 'Deploy to Docker (Swarm or Compose)' description: | Robustly deploys a processed Docker Compose file either to Docker Swarm or regular Docker Compose via SSH. Includes detailed debug output, error handling, and cleanup. inputs: stack_name: description: 'Docker stack or compose project name' required: true ssh_host: description: 'SSH host' required: true ssh_user: description: 'SSH username' required: true ssh_key: description: 'SSH private key' required: true deploy_file: description: 'Path to the processed deployment file' required: true deploy_mode: description: 'Deployment mode: swarm or compose' required: false default: 'swarm' remote_temp_dir: description: 'Remote temporary directory for deployment files' required: false default: '/tmp' copy_build_context: description: 'Whether to copy Docker build context (Dockerfile and related files) to remote host' required: false default: 'false' build_context_path: description: 'Local path to Docker build context (directory containing Dockerfile). Defaults to ./build-context' required: false default: 'build-context' copy_env_file: description: 'Whether to copy .env file to remote host' required: false default: 'false' env_file_path: description: 'Local path to .env file' required: false default: '.env' docker_registry: description: 'Docker registry URL (e.g., ghcr.io). Defaults to Docker Hub if empty.' required: false default: '' docker_username: description: 'Docker registry username' required: false default: '' docker_password: description: 'Docker registry password or token' required: false default: '' runs: using: 'composite' steps: - id: deploy shell: bash run: | set -euo pipefail STACK_NAME="${{ inputs.stack_name }}" SSH_HOST="${{ inputs.ssh_host }}" SSH_USER="${{ inputs.ssh_user }}" DEPLOY_FILE="${{ inputs.deploy_file }}" DEPLOY_MODE="${{ inputs.deploy_mode }}" REMOTE_TEMP_DIR="${{ inputs.remote_temp_dir }}/$STACK_NAME" COPY_BUILD_CONTEXT="${{ inputs.copy_build_context }}" BUILD_CONTEXT_PATH="${{ inputs.build_context_path }}" DOCKER_REGISTRY="${{ inputs.docker_registry }}" DOCKER_USERNAME="${{ inputs.docker_username }}" DOCKER_PASSWORD="${{ inputs.docker_password }}" echo "๐Ÿš€ Starting deployment of '$STACK_NAME' to host '$SSH_HOST' using mode '$DEPLOY_MODE'" # Validate deploy_mode input if [[ "$DEPLOY_MODE" != "swarm" && "$DEPLOY_MODE" != "compose" ]]; then echo "โŒ ERROR: Invalid deploy_mode '$DEPLOY_MODE'. Must be 'swarm' or 'compose'." exit 1 fi # Check if deployment file exists locally if [ ! -f "$DEPLOY_FILE" ]; then echo "โŒ ERROR: Deployment file '$DEPLOY_FILE' does not exist." exit 1 fi # Create temporary SSH key file SSH_KEY_FILE=$(mktemp) echo "${{ inputs.ssh_key }}" > "$SSH_KEY_FILE" chmod 600 "$SSH_KEY_FILE" echo "๐Ÿ”‘ DEBUG: Temporary SSH key created at '$SSH_KEY_FILE'" # Ensure remote directory exists echo "๐Ÿ“ DEBUG: Creating remote directory '$REMOTE_TEMP_DIR'" ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "mkdir -p '$REMOTE_TEMP_DIR' && chmod 700 '$REMOTE_TEMP_DIR'" # Docker Login (if credentials provided) if [[ -n "$DOCKER_USERNAME" && -n "$DOCKER_PASSWORD" ]]; then echo "๐Ÿ” DEBUG: Performing Docker login..." LOGIN_CMD="echo '$DOCKER_PASSWORD' | docker login -u '$DOCKER_USERNAME' --password-stdin" if [[ -n "$DOCKER_REGISTRY" ]]; then LOGIN_CMD="$LOGIN_CMD $DOCKER_REGISTRY" fi ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "$LOGIN_CMD" echo "โœ… DEBUG: Docker login successful" fi # Copy deployment file to remote host echo "๐Ÿ“ค DEBUG: Copying deployment file '$DEPLOY_FILE' to remote host at '$REMOTE_TEMP_DIR/docker-compose.yml'" scp -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$DEPLOY_FILE" \ "$SSH_USER@$SSH_HOST:$REMOTE_TEMP_DIR/docker-compose.yml" # Optionally copy Docker build context if [ "$COPY_BUILD_CONTEXT" == "true" ]; then echo "๐Ÿ“‚ DEBUG: Copying Docker build context from '$BUILD_CONTEXT_PATH' to remote host" scp -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" -r \ "$BUILD_CONTEXT_PATH"/* \ "$SSH_USER@$SSH_HOST:$REMOTE_TEMP_DIR/" echo "๐Ÿ“‹ DEBUG: Listing remote build context directory '$REMOTE_TEMP_DIR'" ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "ls -lha '$REMOTE_TEMP_DIR'" fi # Optionally copy env file COPY_ENV_FILE="${{ inputs.copy_env_file }}" ENV_FILE_PATH="${{ inputs.env_file_path }}" if [ "$COPY_ENV_FILE" == "true" ]; then if [ ! -f "$ENV_FILE_PATH" ]; then echo "โŒ ERROR: .env file '$ENV_FILE_PATH' does not exist." exit 1 fi echo "๐Ÿ“„ DEBUG: Copying .env file from '$ENV_FILE_PATH' to remote host" scp -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$ENV_FILE_PATH" \ "$SSH_USER@$SSH_HOST:$REMOTE_TEMP_DIR/.env" fi # Validate docker-compose file remotely before deploying echo "๐Ÿ” DEBUG: Validating Docker Compose file remotely" ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "docker compose -f '$REMOTE_TEMP_DIR/docker-compose.yml' config --quiet" echo "โœ… DEBUG: Docker Compose file validation succeeded" # Deploy based on mode if [ "$DEPLOY_MODE" == "swarm" ]; then echo "๐Ÿšข DEBUG: Deploying stack '$STACK_NAME' to Docker Swarm" ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "docker compose pull && docker stack deploy -c '$REMOTE_TEMP_DIR/docker-compose.yml' '$STACK_NAME' --with-registry-auth" echo "โœ… DEBUG: Stack '$STACK_NAME' deployed successfully to Docker Swarm" else echo "๐Ÿณ DEBUG: Deploying project '$STACK_NAME' using Docker Compose" ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "cd '$REMOTE_TEMP_DIR' && docker compose pull && docker compose -p '$STACK_NAME' up -d --remove-orphans" echo "โœ… DEBUG: Project '$STACK_NAME' deployed successfully using Docker Compose" fi # Cleanup remote temporary files echo "๐Ÿงน DEBUG: Cleaning up remote temporary directory '$REMOTE_TEMP_DIR'" ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" \ "$SSH_USER@$SSH_HOST" \ "rm -rf '$REMOTE_TEMP_DIR'" # Cleanup local temporary SSH key file rm -f "$SSH_KEY_FILE" echo "๐Ÿ”‘ DEBUG: Temporary SSH key file '$SSH_KEY_FILE' removed" echo "๐ŸŽ‰ Deployment of '$STACK_NAME' completed successfully!"