This repository is configured for a production deployment flow:
- Run tests in GitHub Actions.
- Build Docker image and push to Docker Hub.
- Deploy on VPS by pulling image and recreating Docker Compose services.
- App image:
docker.io/<DOCKERHUB_USERNAME>/devops-tutorial:<tag> - Production compose file:
docker-compose.prod.yml - VPS deploy script:
ops/deploy.sh - CI/CD workflows:
.github/workflows/test.yaml.github/workflows/build.yaml.github/workflows/deploy.yaml.github/workflows/cleanup-dockerhub.yaml
pnpm install
pnpm run start:devdocker build -t devops-tutorial:local .
docker run --rm -p 3000:3000 devops-tutorial:localRun once on VPS:
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-plugin git
sudo usermod -aG docker "$USER"
newgrp dockerClone project and create production env file:
git clone <your-repo-url>
cd devops-ready-production-CI-CD-Workflows
cp ops/.env.production.example ops/.env.productionUpdate ops/.env.production:
DOCKERHUB_USERNAME=your-dockerhub-username
IMAGE_TAG=latest
APP_PORT=3000Manual deploy on VPS:
./ops/deploy.shSet repository Variables:
DOCKERHUB_USERNAME: your Docker Hub usernameDOCKERHUB_CLEANUP_ENABLED: set totrueonly after validating a manual cleanup dry runDOCKERHUB_PROTECTED_TAGS: optional comma-separatedsha-*tags to never delete
Set repository Secrets:
DOCKERHUB_TOKEN: Docker Hub access tokenDOCKERHUB_DELETE_TOKEN: Docker Hub PAT withDeletepermission for cleanup workflowVPS_HOST: VPS host/IPVPS_USER: SSH usernameVPS_SSH_KEY: private key for SSH loginVPS_PORT: SSH port (optional, default22)VPS_APP_DIR: absolute path to project on VPS
Runs on push/PR and validates:
- TypeScript type check
- Unit tests
- Coverage
Runs on main, tags (v*.*.*), or manual dispatch.
Actions:
- Run tests
- Build Docker image
- Push tags to Docker Hub:
latest(default branch)sha-<commit>vX.Y.Z(when git tag is pushed)
Runs automatically when build workflow succeeds, or manually with an image_tag input.
Actions:
- SSH into VPS
git pull --ff-only- Update
ops/.env.productionwithIMAGE_TAGandDOCKERHUB_USERNAME - Run
./ops/deploy.sh:docker compose pulldocker compose up -d --remove-orphans --force-recreate- health check wait
Runs weekly on Sunday at 03:15 UTC and can also be started manually.
The scheduled run is skipped until DOCKERHUB_CLEANUP_ENABLED=true.
Behavior:
- Keeps
latestand release tags likev1.2.3untouched - Only evaluates tags matching
sha-* - Sorts
sha-*tags bylast_updated - Keeps the newest
10by default - Skips any tags listed in
DOCKERHUB_PROTECTED_TAGS - Deletes older
sha-*tags through Docker Hub API v2
Manual run options:
keep_sha_tags: override how manysha-*tags to keepdry_run: preview deletions without removing tagsprotected_tags: comma-separatedsha-*tags to exclude from cleanup
Deploy latest tag:
sed -i 's/^IMAGE_TAG=.*/IMAGE_TAG=latest/' ops/.env.production
./ops/deploy.shDeploy specific commit image tag:
sed -i 's/^IMAGE_TAG=.*/IMAGE_TAG=sha-<commit_sha>/' ops/.env.production
./ops/deploy.sh- Keep
ops/.env.productiononly on VPS. Do not commit real secrets. - If using a reverse proxy (Nginx/Caddy), map external 80/443 to
APP_PORT. - Use a separate Docker Hub PAT for cleanup if you do not want to give delete scope to the build token.
- Keep enough
sha-*tags to cover your rollback window, or pin important ones inDOCKERHUB_PROTECTED_TAGS.
