Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

feat: Refactor GitHub Actions workflow for Docker image build and tra… #7

feat: Refactor GitHub Actions workflow for Docker image build and tra…

feat: Refactor GitHub Actions workflow for Docker image build and tra… #7

Workflow file for this run

name: Build and Deploy
on:
push:
branches:
- main
workflow_dispatch:
env:
NODE_VERSION: '20'
IMAGE_NAME: ${{ secrets.DOCKER_IMAGE_NAME || 'atom-dbro-backend' }}
jobs:
build-and-transfer:
name: Build and Transfer Docker Image
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Verify Docker access
run: |
echo "=== Verifying Docker access ==="
docker ps 2>&1 | head -3 || {
echo "❌ ERROR: Cannot access Docker daemon"
exit 1
}
echo "✅ Docker access verified"
- name: Build Docker image
run: |
echo "📦 Building Docker image: ${{ env.IMAGE_NAME }}:latest"
docker build --no-cache -t ${{ env.IMAGE_NAME }}:latest -f ./Dockerfile .
echo "✅ Docker image built successfully"
- name: Verify image was built
run: |
if ! docker images | grep -q "${{ env.IMAGE_NAME }}.*latest"; then
echo "❌ ERROR: Docker image was not built successfully"
exit 1
fi
echo "✅ Docker image built successfully: ${{ env.IMAGE_NAME }}:latest"
docker images | grep "${{ env.IMAGE_NAME }}"
- name: Export Docker image to tar.gz
run: |
echo "📦 Exporting Docker image to tar.gz..."
docker save ${{ env.IMAGE_NAME }}:latest | gzip > image.tar.gz
echo "📊 Image file info:"
ls -lh image.tar.gz
IMAGE_SIZE=$(du -h image.tar.gz | cut -f1)
echo "Image size: $IMAGE_SIZE"
# Проверяем, что файл создан и не пустой
if [ ! -f image.tar.gz ] || [ ! -s image.tar.gz ]; then
echo "❌ ERROR: Failed to export Docker image"
exit 1
fi
echo "✅ Docker image exported successfully"
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
- name: Validate SSH connection
run: |
SSH_PORT="${DEPLOY_SSH_PORT:-22}"
ssh -o StrictHostKeyChecking=no -p "$SSH_PORT" ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
"echo 'SSH connection successful' && hostname"
env:
DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT || '22' }}
- name: Transfer Docker image to server
run: |
SSH_PORT="${DEPLOY_SSH_PORT:-22}"
echo "📦 Transferring Docker image to server..."
echo "📊 Image file size: $(du -h image.tar.gz | cut -f1)"
# Передаем образ на сервер
scp -o StrictHostKeyChecking=no -P "$SSH_PORT" image.tar.gz ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/tmp/
# Проверяем, что файл успешно передан
ssh -o StrictHostKeyChecking=no -p "$SSH_PORT" ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \
"if [ -f /tmp/image.tar.gz ]; then echo '✅ Image file transferred successfully'; ls -lh /tmp/image.tar.gz; else echo '❌ ERROR: Image file not found on server'; exit 1; fi"
echo "✅ Docker image transferred successfully"
env:
DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT || '22' }}
- name: Cleanup
if: always()
run: |
echo "🧹 Cleaning up temporary files and Docker resources..."
rm -f image.tar.gz
docker rmi ${{ env.IMAGE_NAME }}:latest 2>/dev/null || true
docker builder prune -f || true
echo "✅ Cleanup completed"
deploy:
name: Deploy Application
needs: build-and-transfer
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
- name: Deploy application
run: |
SSH_PORT="${DEPLOY_SSH_PORT:-22}"
ssh -o StrictHostKeyChecking=no -p "$SSH_PORT" ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "export PROJECT_DIR_VALUE='$PROJECT_DIR_VALUE' CONTAINER_NAME='$CONTAINER_NAME' IMAGE_NAME='$IMAGE_NAME' SERVICE_NAME='$SERVICE_NAME' COMPOSE_PROJECT_NAME='$COMPOSE_PROJECT_NAME'; bash -s" << 'REMOTE_SCRIPT'
set -e
IMAGE_NAME="${IMAGE_NAME:-atom-dbro-backend}"
PROJECT_DIR="$PROJECT_DIR_VALUE"
CONTAINER_NAME="${CONTAINER_NAME:-atom-dbro-app}"
SERVICE_NAME="${SERVICE_NAME:-app}"
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-}"
# Проверяем, что переменная PROJECT_DIR установлена
if [ -z "$PROJECT_DIR" ]; then
echo "❌ ERROR: PROJECT_DIR is not set"
exit 1
fi
# Переходим в директорию проекта
echo "📁 Changing to project directory: $PROJECT_DIR"
cd "$PROJECT_DIR" || {
echo "❌ ERROR: Failed to change to project directory: $PROJECT_DIR"
exit 1
}
# Проверяем, что docker-compose.yml существует
if [ ! -f "docker-compose.yml" ]; then
echo "❌ ERROR: docker-compose.yml not found in $PROJECT_DIR"
echo "Current directory: $(pwd)"
echo "Files in directory:"
ls -la || true
exit 1
fi
echo "✅ Found docker-compose.yml in $(pwd)"
# Загружаем образ в Docker
echo "📥 Importing Docker image from tar.gz..."
if [ ! -f /tmp/image.tar.gz ]; then
echo "❌ ERROR: Image file not found: /tmp/image.tar.gz"
exit 1
fi
echo "📊 Image file size: $(du -h /tmp/image.tar.gz | cut -f1)"
docker load -i /tmp/image.tar.gz
# Проверяем, что образ загружен
if ! docker images | grep -q "$IMAGE_NAME.*latest"; then
echo "❌ ERROR: Failed to import Docker image"
exit 1
fi
echo "✅ Docker image imported successfully"
# Удаляем временный файл
rm -f /tmp/image.tar.gz
echo "🧹 Cleaned up temporary image file"
# Очищаем старые версии образа перед деплоем
echo "🧹 Removing old image versions (if any)..."
docker images "$IMAGE_NAME" --format "{{.Repository}}:{{.Tag}} {{.ID}}" | \
grep -v "latest" | \
awk '{print $2}' | \
xargs -r docker rmi -f || echo "No old image versions to remove"
# Создание необходимых сетей Docker (если не существуют)
echo "🌐 Ensuring Docker networks exist..."
docker network create atom-external-network 2>/dev/null || echo "Network atom-external-network already exists"
docker network create atom-internal-network 2>/dev/null || echo "Network atom-internal-network already exists"
# Перезапуск только контейнера приложения с новым образом
echo "🔄 Restarting application container with new image..."
echo "Working directory: $(pwd)"
echo "Container name: $CONTAINER_NAME"
echo "Service name: $SERVICE_NAME"
echo "Docker image: $IMAGE_NAME:latest"
# Убеждаемся, что мы в правильной директории
if [ "$(pwd)" != "$PROJECT_DIR" ]; then
echo "⚠️ Warning: Not in project directory, changing to $PROJECT_DIR"
cd "$PROJECT_DIR" || exit 1
fi
# Проверяем, что docker-compose.yml существует
COMPOSE_FILE="$PROJECT_DIR/docker-compose.yml"
if [ ! -f "$COMPOSE_FILE" ]; then
echo "❌ ERROR: docker-compose.yml not found at $COMPOSE_FILE"
exit 1
fi
# Проверяем, что docker-compose.yml содержит указанный сервис
if ! grep -q "^ $SERVICE_NAME:" "$COMPOSE_FILE"; then
echo "❌ ERROR: Service '$SERVICE_NAME' not found in docker-compose.yml"
echo "Available services:"
grep -E "^ [a-zA-Z-]+:" "$COMPOSE_FILE" || echo "No services found"
exit 1
fi
# Устанавливаем переменные для docker-compose
export DOCKER_IMAGE="$IMAGE_NAME:latest"
echo "✅ DOCKER_IMAGE environment variable set to: $DOCKER_IMAGE"
# Устанавливаем имя проекта (стек) для docker-compose, если указано
if [ -n "$COMPOSE_PROJECT_NAME" ]; then
export COMPOSE_PROJECT_NAME="$COMPOSE_PROJECT_NAME"
echo "✅ COMPOSE_PROJECT_NAME set to: $COMPOSE_PROJECT_NAME"
else
echo "ℹ️ COMPOSE_PROJECT_NAME not set, using default (directory name)"
fi
# Останавливаем и удаляем только контейнер приложения (если существует)
if docker ps -a --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
echo "🛑 Stopping existing container: $CONTAINER_NAME"
docker stop "$CONTAINER_NAME" 2>/dev/null || true
echo "🗑️ Removing existing container: $CONTAINER_NAME"
docker rm "$CONTAINER_NAME" 2>/dev/null || true
else
echo "ℹ️ Container $CONTAINER_NAME does not exist, will be created"
fi
# Проверяем, что образ существует
if ! docker images | grep -q "$IMAGE_NAME.*latest"; then
echo "❌ ERROR: Docker image $IMAGE_NAME:latest not found"
echo "Available images:"
docker images | head -10
exit 1
fi
# Запускаем только указанный сервис с новым образом
# --force-recreate: пересоздает контейнер даже если конфигурация не изменилась
# --no-deps: не запускает зависимости
# --pull never: не пытается скачать образ (он уже загружен)
# -p или --project-name: явно указываем имя проекта (стек)
echo "▶️ Starting service '$SERVICE_NAME' with docker compose..."
if [ -n "$COMPOSE_PROJECT_NAME" ]; then
echo "Command: docker compose -f '$COMPOSE_FILE' -p '$COMPOSE_PROJECT_NAME' up -d --force-recreate --no-deps --pull never '$SERVICE_NAME'"
docker compose -f "$COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" up -d --force-recreate --no-deps --pull never "$SERVICE_NAME"
else
echo "Command: docker compose -f '$COMPOSE_FILE' up -d --force-recreate --no-deps --pull never '$SERVICE_NAME'"
docker compose -f "$COMPOSE_FILE" up -d --force-recreate --no-deps --pull never "$SERVICE_NAME"
fi
# Проверяем, что контейнер запустился
if ! docker ps --format "{{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
echo "❌ ERROR: Container $CONTAINER_NAME failed to start"
echo "Container status:"
docker ps -a | grep "$CONTAINER_NAME" || echo "Container not found"
exit 1
fi
echo "✅ Container $CONTAINER_NAME started successfully"
# Ожидание готовности контейнера
echo "⏳ Waiting for application container to be ready..."
MAX_CONTAINER_WAIT_ATTEMPTS=30
CONTAINER_WAIT_ATTEMPT=0
CONTAINER_READY=false
while [ $CONTAINER_WAIT_ATTEMPT -lt $MAX_CONTAINER_WAIT_ATTEMPTS ]; do
# Проверяем, что контейнер запущен и работает
if docker ps | grep -q "$CONTAINER_NAME" && docker exec "$CONTAINER_NAME" echo "Container is ready" > /dev/null 2>&1; then
CONTAINER_READY=true
echo "✅ Container is ready and accepting commands"
break
fi
CONTAINER_WAIT_ATTEMPT=$((CONTAINER_WAIT_ATTEMPT + 1))
if [ $CONTAINER_WAIT_ATTEMPT -lt $MAX_CONTAINER_WAIT_ATTEMPTS ]; then
echo "Waiting for container to be ready... ($CONTAINER_WAIT_ATTEMPT/$MAX_CONTAINER_WAIT_ATTEMPTS)"
sleep 2
fi
done
if [ "$CONTAINER_READY" != true ]; then
echo "❌ Application container failed to start or is not ready"
echo "📋 Container status:"
docker ps -a | grep "$CONTAINER_NAME" || echo "Container not found"
echo "📋 Container logs:"
docker logs "$CONTAINER_NAME" --tail 50 || true
exit 1
fi
# Даем приложению время на полный запуск
echo "⏳ Waiting for application to fully start (10 seconds)..."
sleep 10
# Очистка неиспользуемых Docker ресурсов (для экономии места)
echo "🧹 Cleaning up unused Docker resources..."
echo "📊 Disk usage before cleanup:"
df -h / | tail -1 || true
# Удаляем старые версии образа (если есть)
docker images "$IMAGE_NAME" --format "{{.Repository}}:{{.Tag}} {{.ID}}" | \
grep -v "latest" | \
awk '{print $2}' | \
xargs -r docker rmi -f || true
# Полная очистка неиспользуемых ресурсов (без volumes для безопасности)
# Удаляет: остановленные контейнеры, неиспользуемые образы, build cache, сети
docker system prune -a -f || echo "⚠️ Warning: Some resources could not be cleaned up"
echo "📊 Disk usage after cleanup:"
df -h / | tail -1 || true
echo "✅ Deployment completed successfully!"
REMOTE_SCRIPT
env:
DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT || '22' }}
PROJECT_DIR_VALUE: ${{ secrets.DEPLOY_PROJECT_PATH }}
CONTAINER_NAME: ${{ secrets.DEPLOY_CONTAINER_NAME || 'atom-dbro-app' }}
SERVICE_NAME: ${{ secrets.DEPLOY_SERVICE_NAME || 'app' }}
IMAGE_NAME: ${{ env.IMAGE_NAME }}
COMPOSE_PROJECT_NAME: ${{ secrets.DEPLOY_COMPOSE_PROJECT_NAME || '' }}