Configuring Automated Deployments with GitHub Actions
A practical guide to setting up GitHub Actions CI/CD pipelines for automated building, testing, and deployment of applications to Linux servers.
Why Automate Deployments
Manual deployments are fragile, inconsistent, and time-consuming. A developer SSH-ing into a server, pulling changes, and restarting services is a routine that scales poorly and invites human error. A CI/CD pipeline automates this workflow: every push to a repository triggers a repeatable, auditable process that builds, tests, and deploys your application.
GitHub Actions is a strong choice for automation because it integrates natively with GitHub repositories, offers generous free-tier minutes, and supports self-hosted runners for private infrastructure. This guide walks through configuring a production-grade deployment pipeline.
Core Concepts
A GitHub Actions workflow is a YAML file stored in .github/workflows/ that defines one or more jobs triggered by events such as push, pull_request, or workflow_dispatch.
Event (e.g., push to main)
└── Job 1: Build
├── Check out code
├── Install dependencies
├── Run tests
└── Build Docker image
└── Job 2: Deploy
├── Authenticate to server
├── Copy artefacts
├── Restart services
└── Verify health
A Basic Build and Test Workflow
Every pipeline should start with building and testing your code. Here is a minimal Node.js example:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
This workflow triggers on every push and pull request, ensuring broken code never reaches the main branch unnoticed.
Building and Publishing Docker Images
Once tests pass, the next step is to package the application. Docker images should be built, tagged, and pushed to a container registry:
docker-build:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
Tagging with both latest and the commit SHA gives you the option to deploy a specific version or roll back to a known-good state.
Deploying to a Linux Server
For deployment to a VPS or bare-metal server, there are several approaches. I recommend SSH-based deployment with a lightweight script runner:
Approach 1: SSH Deploy with Appleboy
deploy:
needs: docker-build
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /opt/applications/my-app
docker compose pull
docker compose up -d --remove-orphans
docker image prune -f
This action SSHes into the server, pulls the latest Docker images, restarts services, and cleans up unused images.
Approach 2: Self-Hosted Runner
For environments where SSH access is restricted or you need tighter integration, register a self-hosted runner on your server:
# On the target server
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux.tar.gz \
-L https://github.com/actions/runner/releases/download/v2.320.0/actions-runner-linux-x64-2.320.0.tar.gz
tar xzf actions-runner-linux.tar.gz
./config.sh --url https://github.com/your-org/your-repo --token <registration-token>
sudo ./svc.sh install && sudo ./svc.sh start
Then reference it in your workflow:
jobs:
deploy:
runs-on: self-hosted
steps:
- uses: actions/checkout@v4
- run: docker compose -f docker-compose.prod.yml up -d --build
Environment-Specific Configuration
A robust pipeline handles multiple environments (staging, production) through GitHub Environments and secrets:
deploy-staging:
environment: staging
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to staging"
- run: ./deploy.sh ${{ vars.STAGING_HOST }}
deploy-production:
environment: production
needs: deploy-staging
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to production"
- run: ./deploy.sh ${{ vars.PROD_HOST }}
GitHub Environments support approval gates, secret isolation, and deployment branch policies — critical for production safety.
Monitoring Pipeline Health
A deployment is not complete until you verify it succeeded. Add health-check steps:
- name: Health Check
run: |
for i in {1..30}; do
curl -sf http://${{ secrets.DEPLOY_HOST }}/health && break
sleep 5
done
This polls the application's health endpoint with a timeout. If the service does not respond within 30 retries (2.5 minutes), the pipeline fails and a rollback should be triggered.
Secrets Management
Never hardcode credentials in workflow files. GitHub provides:
- Repository secrets — Available to all workflows in the repository
- Environment secrets — Scoped to a specific environment
- Organization secrets — Shared across repositories
- OpenID Connect — For cloud provider authentication without static keys
For AWS deployments, use OIDC instead of long-lived access keys:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: ap-south-1
Common Pitfalls
-
Missing cache configuration — Without dependency caching, every build downloads all packages from scratch. Use
actions/cacheor the built-in caching in setup actions to reduce build times by 60–80%. -
Overly permissive secrets — Repository secrets are accessible to all workflows, including those triggered by pull requests from forks. Use environment secrets for sensitive production credentials.
-
No rollback strategy — A failed deployment should not leave the application in an undefined state. Docker Compose with
--remove-orphansand previous image tags enables quick rollbacks. -
Ignoring workflow run concurrency — Multiple pushes in quick succession can cause conflicting deployments. Set
concurrency: deploy-productionin your workflow to cancel in-progress runs.
Conclusion
GitHub Actions provides a powerful, integrated CI/CD platform that works well for small teams and enterprise organisations alike. Start with a simple build-and-test workflow, add Docker image publishing, then layer in SSH-based deployment. Each stage adds reliability and repeatability to your release process, reducing the time between committing code and shipping it to production.