Automating deployments ensures that your production releases are consistent, secure, and fast. In this blog, we’ll walk through a GitHub Actions workflow that deploys a Dockerized application to a remote server, with several key DevOps best practices.
✅ Note: This process is designed for applications you want to deploy and run as containers on a virtual machine (VM).
The app will be copied to the VM, built using Docker Compose, and then run as a containerized service on that machine.
-
Environment-Specific Compose Files
Usedocker-compose.prod.yaml,docker-compose.dev.yaml, etc., to isolate configurations per environment. -
Temporary Rename During Build
Renamedocker-compose.prod.yamltodocker-compose.yamlonly during CI to avoid hardcoding or committing it. -
Docker System Prune
Rundocker system prune -fbefore and afterdocker compose upto clean up unused images, networks, and volumes. -
Secure Credential Handling
Use GitHub Secrets to store SSH credentials instead of hardcoding them into the workflow. -
No Cache Build
Use--no-cacheduring builds to ensure fresh layers are built, avoiding stale behavior. -
Use
.dockerignore
Prevent unnecessary files (like.git,node_modules, etc.) from being copied and bloating Docker images. -
Use
docker compose downBefore Up
Shuts down previous containers cleanly to avoid port conflicts and leftover states. -
Logging Deployments
Pipe the deployment output todeploy.logfor traceability and debugging. -
Use Latest Action Versions
Keep action versions updated (e.g.,actions/checkout@v4) to use the latest security and feature updates.
name: ams-production-deploy
on:
push:
branches:
- production
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Rename Docker Compose file for production
run: mv docker-compose.prod.yaml docker-compose.yaml
- name: Copy project files to remote server
uses: appleboy/scp-action@v0.1.4
with:
source: "."
target: "/app/folder/path"
host: ${{ secrets.PROD_HOST_NAME }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
port: ${{ secrets.PROD_PORT }}
passphrase: ${{ secrets.PROD_PASSPHRASE }}
- name: SSH into server and deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST_NAME }}
port: ${{ secrets.PROD_PORT }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
passphrase: ${{ secrets.PROD_PASSPHRASE }}
script: |
echo "Moving to deployment directory"
cd /app/folder/path
echo "Rebuild and launch with no cache"
docker compose build --no-cache
docker compose up -d > deploy.log 2>&1
echo "Post-deployment cleanup"
docker system prune -f
rm -f docker-compose.yaml