Introduction to Continuous Deployment with GitHub Actions
Understanding the fundamentals of Continuous Deployment with GitHub Actions
Continuous Deployment (CD) is the practice of automatically deploying code changes through multiple environments in a controlled manner. Unlike manual deployments where developers manually push code to servers, CD creates an automated pipeline that moves your code from development to testing to production environments seamlessly. This ensures consistent deployments, reduces human error, and allows teams to deliver features faster while maintaining quality controls at each stage.
In this article, we'll build a simple CD pipeline that demonstrates how code flows from a development environment to a test environment, with proper approval gates and environment-specific configurations. This foundation will prepare you for more complex scenarios involving production deployments and integration with CI processes.
Demo: Sample CD with Python App
Let's create a practical example that shows how CD works by building a Python application that reports which environment it's running in. This will clearly demonstrate that our pipeline is working correctly across different environments.
Project Structure
Our project will have a minimal structure that focuses on the CD pipeline essentials. Since this is part of a series covering different deployment scenarios, we'll name our files specifically to avoid confusion with other examples:
github-actions/
├── .github/
│ └── workflows/
│ └── sample-CD.yaml
├── sample-CD.py
└── README.md
This structure keeps everything simple while providing a clear foundation for understanding how GitHub Actions CD pipelines work. The workflow file will contain all our deployment logic, while the Python script serves as our application that will be deployed across environments.
Creating the Application
We are going to create a really basic application that prints out the environment variable as we deploy from DEV to TEST environments.
sample-CD.py
import os
def main():
"""
Main application function that demonstrates environment-specific deployment.
When deployed, this function will:
- Read the ENVIRONMENT variable set by GitHub Actions
- Print the current environment name to logs
- Provide clear evidence that deployment succeeded in the correct environment
Expected log output:
- Dev environment: "Application is running in: DEV"
- Test environment: "Application is running in: TEST"
- Production environment: "Application is running in: PRODUCTION"
This logging pattern helps verify that:
1. Environment variables are correctly configured
2. Deployment reached the intended environment
3. Application can access environment-specific settings
"""
environment = os.environ.get('ENVIRONMENT', 'unknown')
print(f"Application is running in: {environment.upper()}")
if __name__ == "__main__":
main()
Setting Up GitHub Environments
GitHub Environments are repository-specific configurations that allow you to control how deployments work for different stages of your application lifecycle. Each environment can have its own variables, secrets, and protection rules. This is done at the repository level, meaning every repository manages its own environments independently.
The environment setup serves several critical purposes. First, it provides isolation between different stages of your application, ensuring that development activities don't interfere with testing or production systems. Second, it allows you to configure different settings for each environment, such as database URLs, API keys, or feature flags. Third, it enables protection rules like requiring approvals before deploying to sensitive environments like production.
Development Environment Setup
Navigate to your repository settings and click on "Environments" in the left sidebar. Create a new environment called "dev" and add an environment variable called ENVIRONMENT
with the value dev
. This environment represents your development stage where new features are first deployed and tested by developers.
Test Environment Setup
Create another environment called "test" and set the ENVIRONMENT
variable to test
. For this environment, enable the "Required reviewers" protection rule and add yourself or team members who should approve test deployments. This approval gate ensures that only validated changes move forward in the pipeline and provides a human checkpoint before reaching production-like environments.
Creating the CD Pipeline
Next step is to create the workflow to deploy our code from DEV to TEST environments automatically.
name: Sample CD Pipeline
on:
push:
branches: [main] # Trigger deployment when code is pushed to main branch
jobs:
# DEPLOYMENT STAGE 1: Development Environment
deploy-dev:
name: Deploy to Development
runs-on: ubuntu-latest
environment: dev # This connects to the 'dev' environment
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to Dev Environment
run: |
export ENVIRONMENT="${{ vars.ENVIRONMENT }}"
echo "Starting deployment to Development environment..."
# This is where actual deployment happens - running our application
python sample-CD.py
echo "Development deployment completed successfully!"
# DEPLOYMENT STAGE 2: Test Environment (depends on dev success)
deploy-test:
name: Deploy to Test
runs-on: ubuntu-latest
needs: deploy-dev # This ensures test deployment only runs after dev succeeds
environment: test # This connects to the 'test' environment with approval required
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy to Test Environment
run: |
export ENVIRONMENT="${{ vars.ENVIRONMENT }}"
echo "Starting deployment to Test environment..."
# This is where actual deployment happens - running our application
python sample-CD.py
echo "Test deployment completed successfully"
The YAML file contains clear comments indicating where deployment actually occurs. The needs: deploy-dev
directive creates a dependency chain, ensuring that test deployment only happens if development deployment succeeds. This prevents broken code from advancing through the pipeline.
How the CD Pipeline Works
The pipeline workflow follows a logical sequence that mirrors real-world deployment practices. When you push code to the main branch, GitHub Actions automatically triggers the CD pipeline. However, before code reaches the main branch, it should be thoroughly tested through feature branch development.
Here's the complete development and deployment flow that ensures code quality:
Feature Development Phase: Start by creating a feature branch from main where you develop and test your changes locally. This isolates your work from the main codebase and allows you to experiment freely. During this phase, you should run tests locally and ensure your changes work as expected in a development-like environment.
Code Integration Phase: Once your feature is complete, create a pull request to merge your feature branch into main. This triggers code review processes and automated checks that validate your changes before they enter the main deployment pipeline. This step is crucial for maintaining code quality and catching issues before deployment.
Automated Deployment Phase: After merging to main, the CD pipeline takes over automatically. The development environment deployment runs first, providing an immediate validation that your code works in a controlled environment. This stage uses the development environment configuration and runs without any manual intervention.
Approval and Testing Phase: If the development deployment succeeds, the pipeline proceeds to the test environment deployment. However, this stage requires manual approval due to the protection rules we configured. This gives stakeholders a chance to review the changes and decide whether they're ready for the test environment. After approval, the test deployment runs with test environment configurations, providing a final validation before production readiness.
This multi-stage approach ensures that code is progressively validated through increasingly production-like environments, with appropriate checkpoints for human oversight and quality assurance.
Testing the Pipeline with Feature Branch Development
To demonstrate the complete workflow, let's walk through creating a new feature and deploying it through our CD pipeline:
Step 1: Create and Work on Feature Branch
git checkout -b feature/update-deployment-message
# Make changes to sample-CD.py
git add .
git commit -m "Sample CD with Github Actions"
git push origin feature/update-deployment-message
Step 2: Create Pull Request and Merge Create a pull request from your feature branch to main, review the changes, and merge when ready. This represents the integration point where your feature becomes part of the main codebase.
Step 3: Observe Automatic CD Pipeline After merging, navigate to the Actions tab in your GitHub repository to watch the deployment process:
The development deployment will start immediately and complete automatically. You'll see logs showing "Application is running in: DEV" confirming that the deployment reached the correct environment with proper configuration.
The test deployment will appear as "Waiting for approval" due to the protection rules we configured. Click "Review deployments" and approve the deployment to proceed. After approval, you'll see logs showing "Application is running in: TEST" confirming successful test environment deployment.
Conclusion
Continuous Deployment addresses several critical challenges in software development. Manual deployment processes are error-prone, time-consuming, and often inconsistent between different environments. CD eliminates these issues by creating repeatable, automated processes that deploy code the same way every time.
CD works hand-in-hand with Continuous Integration (CI) to create a complete automated software delivery pipeline. While CI focuses on automatically building and testing code changes, CD takes over once those tests pass and handles the deployment process. Together, CI/CD creates a seamless flow from code commit to production deployment.
The benefits of CD become apparent quickly: faster time to market, reduced deployment risk, consistent deployments across environments, and the ability to roll back quickly if issues arise. Teams can focus on writing code instead of managing deployment procedures, leading to increased productivity and better software quality.