Github Environments with Github Actions and Injecting Environment Values in Docker Containers
Using Github Environments with Github Actions for Multi Environment deployments
Overview
In the world of continuous integration and continuous deployment (CI/CD), managing different environments like development, test, staging, and production is a common practice. This helps isolate changes and ensure the application behaves as expected at each stage before reaching the end user. GitHub Actions, a powerful CI/CD tool, provides a feature called "environments" that simplifies this process. This article explores how GitHub environments can be utilized with GitHub Actions to manage secrets and inject them into Docker containers, a popular choice for packaging and deploying applications.
Why GitHub Environments
GitHub Environments allows logical grouping of the environment variables, secrets for a particular environment where the application is to be deployed. It also enables the user to create the branching rules, like which branch needs to be deployed for that particular environment.
As mentioned above, this article will elaborate and demonstrate with examples on the following topics
Logically grouping the environment variables and secrets for a particular environment on GitHub.
Creating branching rules for the deployment of the particular environment on GitHub.
Injecting the environment variables and secrets into the Docker containers using GitHub actions.
Create GitHub Environment
As described above, GitHub Environments allows logical grouping of the environment variables, secrets for a particular environment. Let’s create an environment and add variables and secrets specific to the environment to be used in the deployment pipelines(GitHub Actions).
Below is a sample dev environment for a repository on GitHub.
To create environments on GitHub, navigate to your repository's Settings tab and then to Environments. The sample below shows that a development environment has been added with a few secrets and environment variables.
Create Branching Rules
Branching rules for the environment would help restrict the CI/CD pipeline(GitHub Actions) to deploy only the listed branches or tags to the specific environment.
Below is the branching rule for the dev environment. The code from the dev branch would be pulled and built using GitHub actions and deployed to the dev stack.
Now that we have seen how the environment could be added and configured on GitHub. Let’s create a GitHub Actions CI/CD pipeline that builds a Node.js application, creates a Docker image for the configured environment, and publishes it to Docker Hub.
Create GitHub Actions Pipeline
name: Node.js CI
on:
push:
branches: [ "dev"]
pull_request:
branches: [ "dev" ]
jobs:
build:
environment: dev
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 20.x ]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build --if-present
test:
runs-on: ubuntu-latest
needs: build
environment: dev
strategy:
matrix:
node-version: [ 20.x ]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test -- --coverage
- name: Archive coverage data
uses: actions/upload-artifact@v4.0.0
with:
name: jest-coverage-data
path: ./coverage
- name: Run codacy-coverage-reporter
uses: codacy/codacy-coverage-reporter-action@v1.3.0
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
coverage-reports: ./coverage/lcov.info
publish:
runs-on: ubuntu-latest
needs: [ build,test ]
environment: dev
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Docker build and push
uses: docker/build-push-action@v2
with:
context: .
push: 'true'
tags: abc/your-app:latest, abc/your-app:${{ github.run_number }}
As mentioned in the GitHub Actions workflow above, there are three jobs in the workflow. Build, Test, and Publish, each job is set with the environment as dev, which indicates the workflow job to use secrets and env variables from the DEV environment added on GitHub. The environment also prevents the workflow job from executing if there is a code push or pull request to a branch other than dev. For more details on creating the CI/CD pipeline using GitHub actions, I would recommend reading this article: CI/CD Pipeline GitHub Actions.
Inject Environment Variables in Docker Container
To inject the GitHub environment variables into the Docker container. Let’s add build-args to the Docker Build and Push step in the Publish Job as in the above GitHub Actions workflow.
Let’s consider the app uses MongoDB as the datastore; it would require the environment-specific DB URL to connect to the MongoDB database. Here, we would add the MONGO_DB_URL as a secret to the GitHub environment to make it available for the job to access the secret value and set it as a build arg while building the Docker image.
Add a secret to the GitHub Environment
Add build-args to Publish Job
publish:
runs-on: ubuntu-latest
needs: [ build,test ]
environment: dev
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Docker build and push
uses: docker/build-push-action@v2
with:
build-args: |
MONGO_DB_URL: ${{secrets.MONGO_DB_URL}}
context: .
push: 'true'
tags: abc/your-app:latest, abc/your-app:${{ github.run_numbe
Dockerfile change to read and set environment variables
FROM node:20-alpine
ARG MONGO_DB_URL
ENV MONGO_DB_URL=$MONGO_DB_URL
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
COPY package*.json ./
USER node
RUN npm install
COPY --chown=node:node . .
EXPOSE 8080
CMD [ "node", "index.js" ]
The NodeJS application running in the Docker container can now use this environment variable to set the MONGO_DB_URL value, allowing it to connect to the specific database.
Note: There are better ways to inject environment variables into Docker containers. The purpose of the above example is to show how one can inject environment variables into Docker containers.
Conclusion
By combining the power of GitHub Environments and GitHub Actions, we can build a secure and automated deployment pipeline. GitHub Environments provide a secure vault for our sensitive configuration, while GitHub Actions provide the mechanism to build, push, and deploy our Docker containers. The ability to inject environment values directly into the running containers ensures that our applications are properly configured for each deployment target without compromising security. This approach is a cornerstone of modern DevOps practices, enabling teams to deploy with confidence and speed.