使用 Bash 脚本自动化 Docker 化应用部署
Source: Dev.to
DevOps 的核心在于自动化、可靠性和效率。在我的 DevOps 实习的第一阶段,我编写了一个强大的 Bash 脚本,用于自动将 Docker 化的应用部署到远程 Linux 服务器。
无论你是开发者、系统管理员,还是 DevOps 爱好者,本指南都会一步步带你创建一个生产就绪的部署脚本,涵盖从克隆仓库到将 Nginx 配置为反向代理的全部流程。
为什么要自动化部署?
- 一致性 – 每次部署都完全相同。
- 速度 – 减少人为干预,节省时间。
- 可靠性 – 及早发现错误并记录日志,便于排查。
阅读完本指南后,你将拥有一个单一的 Bash 脚本(deploy.sh),能够完整地将你的 Docker 化应用部署到远程服务器。
第 1 步:收集用户输入的参数
- Git 仓库 URL
- 个人访问令牌(PAT)
- 分支名称(默认
main) - SSH 凭证(用户名、服务器 IP、密钥路径)
- 应用端口
我们还会对输入进行校验,以防脚本后续执行失败。
# Git Repository URL
while [[ -z "${GIT_REPO_URL:-}" ]]; do
read -rp "Enter the git repository URL: " GIT_REPO_URL
[[ -z "$GIT_REPO_URL" ]] && log_error "Repository URL cannot be empty."
done
# Personal Access Token (PAT) - hidden input
while [[ -z "${GIT_PAT:-}" ]]; do
read -rsp "Enter your Git Personal Access Token (PAT): " GIT_PAT
echo ""
[[ -z "$GIT_PAT" ]] && log_error "Personal Access Token cannot be empty."
done
# Branch name (default = main)
read -rp "Enter branch name [default: main]: " GIT_BRANCH
GIT_BRANCH=${GIT_BRANCH:-main}
# SSH username
while [[ -z "${SSH_USER:-}" ]]; do
read -rp "Enter remote server SSH username: " SSH_USER
[[ -z "$SSH_USER" ]] && log_error "SSH username cannot be empty."
done
# Server IP address
while [[ -z "${SERVER_IP:-}" ]]; do
read -rp "Enter remote server IP address: " SERVER_IP
if [[ ! "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
log_error "Invalid IP format. Please enter a valid IPV4 address."
SERVER_IP=""
fi
done
# SSH key path
while [[ -z "${SSH_KEY_PATH:-}" ]]; do
read -rp "Enter path to SSH private key: " SSH_KEY_PATH
if [[ ! -f "$SSH_KEY_PATH" ]]; then
log_error "SSH key file not found at $SSH_KEY_PATH"
SSH_KEY_PATH=""
fi
done
# Application port (internal container port)
while [[ -z "${APP_PORT:-}" ]]; do
read -rp "Enter internal application (container) port (e.g., 8080): " APP_PORT
if ! [[ "$APP_PORT" =~ ^[0-9]+$ ]]; then
log_error "Invalid port. Please enter a numeric value."
APP_PORT=""
fi
done
提示: 始终使用条件语句验证用户输入,确保 URL 合法且文件存在。
第 2 步:克隆或拉取仓库
REPO_NAME=$(basename -s .git "$GIT_REPO_URL")
WORK_DIR="$HOME/deployment/$REPO_NAME"
# Create deployment directory if not exists
mkdir -p "$(dirname "$WORK_DIR")"
# Authenticated URL (PAT safely embedded)
GIT_USERNAME=$(echo "$GIT_REPO_URL" | awk -F[/:] '{print $(NF-1)}')
AUTH_REPO_URL=$(echo "$GIT_REPO_URL" | sed "s#https://#https://$GIT_USERNAME:$GIT_PAT@#")
if [[ -d "$WORK_DIR/.git" ]]; then
log_info "Repository already exists. Pulling latest changes..."
cd "$WORK_DIR"
git reset --hard
git clean -fd
git fetch origin "$GIT_BRANCH"
git checkout "$GIT_BRANCH"
git pull origin "$GIT_BRANCH" || {
log_error "Failed to pull latest changes from $GIT_BRANCH"
exit $EXIT_UNKNOWN
}
else
log_info "Cloning repository into $WORK_DIR..."
git clone --branch "$GIT_BRANCH" "$AUTH_REPO_URL" "$WORK_DIR" || {
log_error "Failed to clone repository. Please check your URL or PAT"
exit $EXIT_UNKNOWN
}
cd "$WORK_DIR"
fi
log_success "Repository is ready at: $WORK_DIR"
此步骤确保本地项目文件夹始终与远程仓库保持最新。
第 3 步:验证 Docker 配置文件
# Check for Docker configuration files
if [[ -f "Dockerfile" ]]; then
log_success "Found Dockerfile – ready for Docker build."
elif [[ -f "compose.yaml" || -f "compose.yml" || -f "docker-compose.yaml" || -f "docker-compose.yml" ]]; then
log_success "Found docker-compose.yml – ready for multi‑service deployment."
else
log_error "No Dockerfile or docker-compose.yml found. Cannot continue deployment."
exit $EXIT_UNKNOWN
fi
第 4 步:测试 SSH 连接
# Validate SSH key exists
if [ ! -f "$SSH_KEY_PATH" ]; then
log_error "SSH key not found at: $SSH_KEY_PATH"
exit $EXIT_SSH_FAIL
fi
# Test SSH connection (non‑interactive)
ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no -o BatchMode=yes \
"$SSH_USER@$SERVER_IP" "echo 'SSH connection successful!'" >/dev/null 2>&1
if [ $? -ne 0 ]; then
log_error "Unable to connect to remote server via SSH. Verify credentials, key permissions, and IP address."
exit $EXIT_SSH_FAIL
else
log_success "SSH connection verified successfully."
fi
第 5 步:准备远程环境
脚本会更新软件包,安装 Docker、Docker Compose 和 Nginx,将 SSH 用户加入 Docker 组,并确保服务已启用并启动。
ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" bash /dev/null; then
echo "Docker not found. Installing Docker..."
curl -fsSL https://get.docker.com | sudo bash
else
echo "Docker already installed."
fi
# Install Docker Compose if not present
if ! command -v docker-compose &>/dev/null; then
echo "Installing Docker Compose..."
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
else
echo "Docker Compose already installed."
fi
# Install Nginx if not present
if ! command -v nginx &>/dev/null; then
echo "Installing nginx..."
sudo apt-get install -y nginx
else
echo "nginx already installed."
fi
# Add SSH user to docker group
if ! groups $SSH_USER | grep -q docker; then
echo "Adding user '$SSH_USER' to docker group..."
sudo usermod -aG docker $SSH_USER
echo "You may need to log out and back in for this to take effect."
else
echo "User '$SSH_USER' already in docker group."
fi
# Enable and start services
sudo systemctl enable docker
sudo systemctl start docker
sudo systemctl enable nginx
sudo systemctl start nginx
# Confirm installation versions
docker --version
docker-compose --version
nginx -v
echo "Remote environment setup complete."
EOF
最佳实践: 在配置完毕后始终确认安装版本和服务状态。
第 6 步:部署 Docker 化的应用
# Sync source code to remote server (excluding unnecessary files)
rsync -avz -e "ssh -i $SSH_KEY_PATH -o StrictHostKeyChecking=no" \
--exclude '.git' --exclude 'node_modules' --exclude '.env' \
"$WORK_DIR/" "$SSH_USER@$SERVER_IP:$REMOTE_APP_DIR" >/dev/null 2>&1
# Run deployment commands on the remote host
ssh -i "$SSH_KEY_PATH" -o StrictHostKeyChecking=no "$SSH_USER@$SERVER_IP" bash <<'EOF'
set -e
cd $REMOTE_APP_DIR
# Detect Docker configuration
if [[ -f "docker-compose.yml" ]]; then
echo "docker-compose.yml found. Starting with Docker Compose..."
sudo docker-compose pull
sudo docker-compose build
sudo docker-compose up -d
elif [[ -f "Dockerfile" ]]; then
echo "Dockerfile found. Building and running manually..."
APP_NAME=$(basename $(pwd))
sudo docker build -t $APP_NAME .
sudo docker run -d -p $APP_PORT:$APP_PORT --name $APP_NAME $APP_NAME
else
echo "No Dockerfile or docker-compose.yml found in $REMOTE_APP_DIR"
exit 1
fi
EOF
此时,应用应已在远程服务器上运行,并可通过已配置的 Nginx 反向代理访问。