Skip to content

docs: add Let's Encrypt SSL/TLS to tech stack and deployment section #54

docs: add Let's Encrypt SSL/TLS to tech stack and deployment section

docs: add Let's Encrypt SSL/TLS to tech stack and deployment section #54

Workflow file for this run

name: CI/CD
# Cancel any in-progress run for the same branch when a new push arrives.
# Prevents two pipeline runs from piling up on rapid commits.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
# ── Detect which parts of the codebase changed ─────────────────────────────
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
infra: ${{ steps.filter.outputs.infra }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
infra:
- 'scripts/**'
- 'nginx/**'
- 'docker-compose.prod.yml'
# ── Run Java tests (only when backend code changed) ────────────────────────
test:
name: Test
needs: changes
if: needs.changes.outputs.backend == 'true'
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15-alpine
env:
POSTGRES_DB: saas_test
POSTGRES_USER: saas_user
POSTGRES_PASSWORD: saas_password
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Set up Java 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: temurin
cache: maven
- name: Run tests
working-directory: backend
env:
SPRING_PROFILES_ACTIVE: test
SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/saas_test
SPRING_DATASOURCE_USERNAME: saas_user
SPRING_DATASOURCE_PASSWORD: saas_password
SPRING_DATA_REDIS_HOST: localhost
SPRING_DATA_REDIS_PORT: '6379'
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
AWS_REGION: ap-south-1
JWT_SECRET: dGVzdC1qd3Qtc2VjcmV0LW11c3QtYmUtbG9uZy1lbm91Z2gtZm9yLUhTMjU2LWFsZ29yaXRobQ==
run: mvn test -B --no-transfer-progress -T 1C
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: backend/target/surefire-reports/
retention-days: 7
# ── Build & push backend Docker image ──────────────────────────────────────
build:
name: Build and Push Backend
runs-on: ubuntu-latest
needs: [changes, test]
if: |
always() &&
github.event_name == 'push' && github.ref == 'refs/heads/main' &&
needs.changes.outputs.backend == 'true' &&
needs.test.result == 'success'
steps:
- uses: actions/checkout@v4
- name: Set up Java 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: temurin
cache: maven
- name: Build JAR
working-directory: backend
run: mvn package -DskipTests -B --no-transfer-progress -T 1C
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./backend
file: ./backend/Dockerfile.ci
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/saas-backend:latest
${{ secrets.DOCKER_USERNAME }}/saas-backend:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# ── Build & push frontend Docker image ─────────────────────────────────────
build-frontend:
name: Build and Push Frontend
runs-on: ubuntu-latest
needs: [changes, test]
if: |
always() &&
github.event_name == 'push' && github.ref == 'refs/heads/main' &&
needs.changes.outputs.frontend == 'true' &&
(needs.test.result == 'success' || needs.test.result == 'skipped')
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: |
${{ secrets.DOCKER_USERNAME }}/saas-frontend:latest
${{ secrets.DOCKER_USERNAME }}/saas-frontend:${{ github.sha }}
build-args: |
VITE_API_BASE_URL=
cache-from: type=gha
cache-to: type=gha,mode=max
# ── Deploy to EC2 ────────────────────────────────────────────────────────────
deploy:
name: Deploy to EC2
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [changes, build, build-frontend]
if: |
always() &&
github.event_name == 'push' && github.ref == 'refs/heads/main' &&
(needs.build.result == 'success' || needs.build.result == 'skipped') &&
(needs.build-frontend.result == 'success' || needs.build-frontend.result == 'skipped') &&
needs.build.result != 'failure' &&
needs.build-frontend.result != 'failure' &&
(
needs.changes.outputs.backend == 'true' ||
needs.changes.outputs.frontend == 'true' ||
needs.changes.outputs.infra == 'true'
)
environment: production
steps:
- uses: actions/checkout@v4
- name: Set up SSH
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.EC2_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
cat >> ~/.ssh/config <<'EOF'
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
ConnectTimeout 15
ServerAliveInterval 30
ServerAliveCountMax 3
EOF
- name: Deploy
env:
DOCKER_IMAGE: ${{ secrets.DOCKER_USERNAME }}/saas-backend
DOCKER_FRONTEND_IMAGE: ${{ secrets.DOCKER_USERNAME }}/saas-frontend
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
IMAGE_TAG: ${{ github.sha }}
EC2_HOST: "65.1.133.248"
EC2_USER: ubuntu
run: SSH_KEY=~/.ssh/deploy_key bash scripts/deploy.sh