Skip to main content

Cleanup DigitalOcean App Workflow

This GitHub Action workflow automatically cleans up preview environments from DigitalOcean App Platform when a pull request is closed or a branch is deleted.

Trigger Conditions

  • Runs when pull requests are closed
  • Runs when branches are deleted

Workflow Steps

1. Environment Setup

  • Checks out code
  • Installs and authenticates doctl CLI
  • Sanitizes branch name to match the naming convention used during deployment

2. Database Cleanup

  • Drops the branch-specific database from the droplet
  • Database name follows pattern: {SANITIZED_BRANCH}_iba

3. App Service Cleanup

  • Finds the app service using the branch name
  • Force deletes the app if it exists

4. Admin Service Cleanup

  • Finds the admin service using the branch name
  • Force deletes the admin app if it exists

5. Error Handling

  • Notifies on failure if deletion encounters errors

Complete Workflow

name: Clean Up DigitalOcean App Platform

on:
pull_request:
types: [closed]
delete:
refs:
- branches/**

jobs:
clean_up:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v3

- name: Install doctl
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DO_API_TOKEN }}

- name: Authenticate doctl
run: doctl auth init -t ${{ secrets.DO_API_TOKEN }}

- name: Sanitize Branch Name
id: sanitize_branch
run: |
ORIGINAL_BRANCH="${{ github.head_ref }}"
SANITIZED_BRANCH=$(echo "${ORIGINAL_BRANCH}" | sed 's/[^a-zA-Z0-9]/-/g')
SANITIZED_BRANCH=${SANITIZED_BRANCH:0:18}
echo "SANITIZED_BRANCH=${SANITIZED_BRANCH}" >> $GITHUB_OUTPUT

- name: Drop Database
env:
SANITIZED_BRANCH: ${{ steps.sanitize_branch.outputs.SANITIZED_BRANCH }}
MANAGED_DB_HOST: ${{ secrets.MANAGED_DB_HOST }}
MANAGED_DB_PORT: ${{ secrets.MANAGED_DB_PORT }}
MANAGED_DB_USER: ${{ secrets.MANAGED_DB_USER }}
MANAGED_DB_PASSWORD: ${{ secrets.MANAGED_DB_PASSWORD }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DO_DROPLET_IP: ${{ secrets.DO_DROPLET_IP }}
DO_DROPLET_PASSWORD: ${{ secrets.DO_DROPLET_PASSWORD }}
run: |
NEW_DB="${SANITIZED_BRANCH}_iba"
echo "New database name: ${NEW_DB}"

sshpass -p "${DO_DROPLET_PASSWORD}" ssh -o StrictHostKeyChecking=no root@${DO_DROPLET_IP} << EOF
set -ex
echo "Dropping existing database \${NEW_DB} (if exists) and creating new database..."
docker exec -i mysql-container mysql -uroot -p"${DB_PASSWORD}" -e "DROP DATABASE IF EXISTS \`${NEW_DB}\`"
EOF

- name: Get App ID
id: get_app
run: |
APP_NAME="${{ steps.sanitize_branch.outputs.SANITIZED_BRANCH }}-app-preview"
APP_ID=$(doctl apps list --format Spec.Name,ID --no-header | awk -v app="$APP_NAME" '$1 == app {print $2}')
echo "APP_ID=${APP_ID}" >> $GITHUB_OUTPUT

- name: Delete App
if: steps.get_app.outputs.APP_ID != ''
run: |
APP_ID="${{ steps.get_app.outputs.APP_ID }}"
doctl apps delete "$APP_ID" --force
echo "App with ID $APP_ID has been deleted."

- name: Get Admin ID
id: get_admin
run: |
ADMIN_NAME="${{ steps.sanitize_branch.outputs.SANITIZED_BRANCH }}-admin-preview"
ADMIN_ID=$(doctl apps list --format Spec.Name,ID --no-header | awk -v app="$ADMIN_NAME" '$1 == app {print $2}')
echo "ADMIN_ID=${ADMIN_ID}" >> $GITHUB_OUTPUT

- name: Delete Admin
if: steps.get_admin.outputs.ADMIN_ID != ''
run: |
echo "${{ steps.get_admin.outputs.ADMIN_ID }}"
ADMIN_ID="${{ steps.get_admin.outputs.ADMIN_ID }}"
doctl apps delete "$ADMIN_ID" --force
echo "App with ID $ADMIN_ID has been deleted."

- name: Notify Failure on Deletion Error
if: failure()
run: |
echo "Failed to delete the app. Please check the logs for more details."

Required Secrets

The workflow requires the following GitHub secrets:

  • DigitalOcean: DO_API_TOKEN, DO_DROPLET_IP, DO_DROPLET_PASSWORD
  • Database: MANAGED_DB_HOST, MANAGED_DB_PORT, MANAGED_DB_USER, MANAGED_DB_PASSWORD, DB_PASSWORD

Cleanup Process

  1. Database Cleanup: Removes the branch-specific database to free up resources
  2. App Cleanup: Deletes both app and admin services from DigitalOcean App Platform
  3. Force Deletion: Uses --force flag to skip confirmation prompts
  4. Error Handling: Continues cleanup even if some components don't exist

Notes

  • The workflow runs automatically when PRs are closed or branches are deleted
  • It uses the same branch name sanitization logic as the deployment workflow
  • Cleanup is idempotent - it safely handles cases where resources don't exist