Add CI/CD pipeline with Harbor registry and SSH deployment
Some checks failed
CI/CD Pipeline - Apartment Dashboard / Run Linter (push) Failing after 4m52s
CI/CD Pipeline - Apartment Dashboard / Build & Push Image (push) Has been skipped
CI/CD Pipeline - Apartment Dashboard / Deploy to Production (push) Has been skipped

- Replace local deployment with proper CI/CD pipeline
- Add lint job as first stage (fail-fast)
- Build and push Docker image to Harbor registry
- Deploy via SSH with image tag from commit SHA
- Add deployment verification with health check
- Update Dockerfile to Node 20 and fix npm ci
- Support IMAGE variable in docker-compose for deployments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-22 16:59:51 -07:00
parent d033483633
commit de08b3ddfa
3 changed files with 147 additions and 58 deletions

View File

@ -1,61 +1,149 @@
name: Deploy Apartment Dashboard
name: CI/CD Pipeline - Apartment Dashboard
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
env:
NODE_VERSION: '20'
IMAGE_NAME: apartment-dashboard
jobs:
deploy:
# ============================================================
# Lint Job - Runs first, blocks everything if linting fails
# ============================================================
lint:
name: Run Linter
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Build Docker image
run: |
docker build -t apartment-dashboard:${{ github.sha }} .
docker tag apartment-dashboard:${{ github.sha }} apartment-dashboard:latest
- name: Deploy to server
run: |
# Copy files to deployment directory
sudo mkdir -p /media/stephen/Storage_Linux/infrastructure/services/apartments
sudo cp -r . /media/stephen/Storage_Linux/infrastructure/services/apartments/
# Navigate to deployment directory
cd /media/stephen/Storage_Linux/infrastructure/services/apartments
# Stop existing container if running
sudo docker compose down || true
# Start new container
sudo docker compose up -d
# Clean up old images
sudo docker image prune -f
- name: Verify deployment
run: |
# Wait a moment for container to start
sleep 10
# Check if container is running
cd /media/stephen/Storage_Linux/infrastructure/services/apartments
sudo docker compose ps
# Test endpoint (optional)
curl -f http://localhost:80 || echo "Container may still be starting..."
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
# ============================================================
# Build & Push Job - Build image and push to Harbor
# ============================================================
build:
name: Build & Push Image
runs-on: ubuntu-latest
needs: lint
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
outputs:
image_tag: ${{ steps.set-tag.outputs.tag }}
full_image: ${{ steps.set-tag.outputs.full_image }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set image tag
id: set-tag
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
echo "tag=${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "full_image=${{ secrets.HARBOR_REGISTRY }}/${{ secrets.HARBOR_PROJECT }}/${{ env.IMAGE_NAME }}:${SHORT_SHA}" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Harbor
uses: docker/login-action@v3
with:
registry: ${{ secrets.HARBOR_REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ secrets.HARBOR_REGISTRY }}/${{ secrets.HARBOR_PROJECT }}/${{ env.IMAGE_NAME }}:${{ steps.set-tag.outputs.tag }}
${{ secrets.HARBOR_REGISTRY }}/${{ secrets.HARBOR_PROJECT }}/${{ env.IMAGE_NAME }}:latest
# ============================================================
# Deploy Job - Pull image and restart on production server
# ============================================================
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || 22 }}
script_stop: true
script: |
set -e
# Image to deploy (using specific SHA tag, not latest)
IMAGE="${{ needs.build.outputs.full_image }}"
echo "Deploying image: $IMAGE"
# Navigate to deployment directory
cd ${{ secrets.DEPLOY_PATH }}
# Pull the specific image (using pre-configured Docker credentials)
docker pull "$IMAGE"
# Update and restart with new image
export IMAGE
docker compose up -d
# Clean up old images
docker image prune -f
# Wait for container to start
echo "Waiting for container to start..."
sleep 10
# Verify container is running
if docker compose ps | grep -q "Up"; then
echo "Container is running successfully"
else
echo "ERROR: Container failed to start"
docker compose logs --tail=50
exit 1
fi
- name: Verify deployment
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || 22 }}
script: |
# Test health endpoint (frontend serves index.html)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://apartments.maverickapplications.com/ || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
echo "Health check passed (HTTP $HTTP_CODE)"
else
echo "WARNING: Health check returned HTTP $HTTP_CODE"
echo "Container may still be initializing..."
exit 1
fi

View File

@ -1,13 +1,13 @@
# Build stage
FROM node:18-alpine as build
FROM node:20-alpine as build
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Install dependencies (including devDependencies needed for build)
RUN npm ci
# Copy source code
COPY . .

View File

@ -1,14 +1,15 @@
version: '3.8'
services:
apartment-dashboard:
image: ${IMAGE:-apartment-dashboard:latest}
build: .
container_name: apartment-dashboard
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.apartments.rule=Host(`apartments.maverickapplications.com`)"
- "traefik.http.routers.apartments.entrypoints=websecure"
- "traefik.http.routers.apartments.tls=true"
- "traefik.http.routers.apartments.tls.certresolver=letsencrypt"
- "traefik.http.services.apartments.loadbalancer.server.port=80"
networks:
@ -16,4 +17,4 @@ services:
networks:
traefik:
external: true
external: true