|
| 1 | +--- |
| 2 | +title: An Introduction to CI with Docker and GitHub Actions |
| 3 | +linkTitle: Docker and GitHub Actions |
| 4 | +params: |
| 5 | + tags: [devops] |
| 6 | + time: 10 minutes |
| 7 | +--- |
| 8 | + |
| 9 | +This guide provides an introduction to building CI pipelines using Docker and |
| 10 | +GitHub Actions. You'll learn how to automate Docker builds, run containerized |
| 11 | +tests, and optimize your workflows for enhanced security and efficiency. |
| 12 | + |
| 13 | +You will learn how to: |
| 14 | + |
| 15 | +- Use Docker's official GitHub Actions to test and build your application. |
| 16 | +- Run unit tests with multi-stage builds. |
| 17 | +- Extract metadata and automatically generate tags and annotations for your Docker images. |
| 18 | +- Configure your workflow to conditionally push images based on the event type (not pushing on pull requests). |
| 19 | +- Generate Software Bill of Materials (SBOM) and provenance attestations to enhance security and traceability. |
| 20 | + |
| 21 | +By the end of the guide, you'll have a simple, functional GitHub Actions |
| 22 | +configuration for Docker builds. Use it as-is, or extend it further to fit your |
| 23 | +needs. |
| 24 | + |
| 25 | +## Prerequisites |
| 26 | + |
| 27 | +If you want to follow along with the guide, ensure you have the following: |
| 28 | + |
| 29 | +- A Docker account. |
| 30 | +- Familiarity with Dockerfiles. |
| 31 | +- A GitHub repository with a Dockerized application. |
| 32 | + |
| 33 | +This guide assumes basic knowledge of Docker concepts but provides explanations |
| 34 | +for using Docker in GitHub Actions workflows. |
| 35 | + |
| 36 | +## Setting up your GitHub Actions workflow |
| 37 | + |
| 38 | +GitHub Actions workflows define a series of steps to automate tasks, such as |
| 39 | +building and pushing Docker images, in response to triggers like commits or |
| 40 | +pull requests. In this guide, the workflow focuses on automating Docker builds |
| 41 | +and testing, ensuring your containerized application works correctly before |
| 42 | +publishing it. |
| 43 | + |
| 44 | +Create a file named `docker-ci.yml` in the `.github/workflows/` directory of |
| 45 | +your repository. Start with the basic workflow configuration: |
| 46 | + |
| 47 | +```yaml |
| 48 | +name: Build and Push Docker Image |
| 49 | + |
| 50 | +on: |
| 51 | + push: |
| 52 | + branches: |
| 53 | + - main |
| 54 | + pull_request: |
| 55 | +``` |
| 56 | +
|
| 57 | +This configuration runs the workflow on pushes to the main branch and on pull |
| 58 | +requests. By including both triggers, you can build and test your application |
| 59 | +on feature branches and ensure that only approved code is pushed to your |
| 60 | +container registry. |
| 61 | +
|
| 62 | +## Run tests |
| 63 | +
|
| 64 | +A key step in CI is validating your application. By integrating tests into your |
| 65 | +Docker image's build process, you ensure that the application behaves as |
| 66 | +expected in a containerized environment. This reduces the risk of errors caused |
| 67 | +by differences between development and production environments. |
| 68 | +
|
| 69 | +You'll use the `docker/build-push-action` to run tests as part of the image |
| 70 | +build. This involves adding a test stage to your Dockerfile that runs tests |
| 71 | +during the build process. If the tests pass, the workflow proceeds to build the |
| 72 | +production image and push it to your container registry. |
| 73 | + |
| 74 | +### Using Docker Build to run tests |
| 75 | + |
| 76 | +Aside from producing images, Docker builds provide isolated environments for |
| 77 | +running tests. For an application's unit tests, it's best practice to define a |
| 78 | +build stage in your Dockerfile that lets you run your tests in a containerized |
| 79 | +environment with a `docker build` invocation, such as: |
| 80 | + |
| 81 | +```console |
| 82 | +$ docker build --target tests . |
| 83 | +``` |
| 84 | + |
| 85 | +For example, consider the following Dockerfile: |
| 86 | + |
| 87 | +```dockerfile |
| 88 | +# builder installs dependencies and builds the app |
| 89 | +FROM node:lts-alpine AS builder |
| 90 | +WORKDIR /src |
| 91 | +COPY package*.json ./ |
| 92 | +RUN npm ci |
| 93 | +COPY . . |
| 94 | +
|
| 95 | +# test runs the unit tests |
| 96 | +FROM builder AS tests |
| 97 | +RUN npm test |
| 98 | +
|
| 99 | +# production builds the final image |
| 100 | +FROM node:lts-alpine AS production |
| 101 | +WORKDIR /app |
| 102 | +COPY --from=builder /src . |
| 103 | +CMD ["npm", "start"] |
| 104 | +``` |
| 105 | + |
| 106 | +In this Dockerfile: |
| 107 | + |
| 108 | +- The first stage, `builder`, installs dependencies and copies the app's source code. |
| 109 | +- The `tests` stage runs tests using the `npm test` command. |
| 110 | +- The `production` stage copies the application from the `builder` stage and |
| 111 | + sets the command to start the application. |
| 112 | + |
| 113 | +### Run tests with GitHub Actions |
| 114 | + |
| 115 | +In your workflow file (`docker-ci.yml`), configure the job to build and test |
| 116 | +your image using the `docker/build-push-action` by adding the following job: |
| 117 | + |
| 118 | +```yaml |
| 119 | +jobs: |
| 120 | + build: |
| 121 | + runs-on: ubuntu-latest |
| 122 | + steps: |
| 123 | + - name: Test the app |
| 124 | + uses: docker/build-push-action@v6 |
| 125 | + with: |
| 126 | + target: tests |
| 127 | +``` |
| 128 | + |
| 129 | +This configuration assumes that your Dockerfile contains a stage named `tests` |
| 130 | +that executes your unit tests. The `docker/build-push-action` builds the |
| 131 | +`tests` stage, and if the tests fail during the build, the action fails, and |
| 132 | +the workflow stops. |
| 133 | + |
| 134 | +Since the action uses the Git context by default, you don't need to check out |
| 135 | +the repository explicitly. The `docker/build-push-action` [automatically detects](https://github.com/docker/build-push-action?tab=readme-ov-file#git-context) |
| 136 | +the Git reference of the GitHub Actions workflow and builds with that |
| 137 | +repository as its context. |
| 138 | + |
| 139 | +## Extract metadata for tags and annotations |
| 140 | + |
| 141 | +To automate the tagging and labeling of your Docker images, use the |
| 142 | +`docker/metadata-action`. This action extracts metadata from your Git |
| 143 | +repository, such as branch names and commit SHAs, and uses them to generate |
| 144 | +tags and labels for your images. |
| 145 | + |
| 146 | +Add the metadata action to your workflow: |
| 147 | + |
| 148 | +```yaml |
| 149 | +- name: Extract Docker image metadata |
| 150 | + id: meta |
| 151 | + uses: docker/metadata-action@v5 |
| 152 | + with: |
| 153 | + images: my-dockerhub-user/my-image |
| 154 | +``` |
| 155 | + |
| 156 | +> [!NOTE] |
| 157 | +> Replace `my-dockerhub-user/my-image` in the `images` field with your image |
| 158 | +> repository name. |
| 159 | + |
| 160 | +This step prepares the metadata for use in tagging and labeling your images |
| 161 | +during the build and push process. |
| 162 | + |
| 163 | +- The `images` input defines the image repository and name to use for tags and |
| 164 | + annotations. |
| 165 | +- The `id` property assigns a referable ID for use in later steps to build with |
| 166 | + the extracted metadata values. |
| 167 | + |
| 168 | +## Authenticate to your registry |
| 169 | + |
| 170 | +Before you build the image, you'll want to authenticate to your registry. |
| 171 | +Authentication is required to push your built image to the registry. |
| 172 | + |
| 173 | +To authenticate with Docker Hub, add the following step to your workflow. |
| 174 | + |
| 175 | +```yaml |
| 176 | +- name: Log in to Docker Hub |
| 177 | + uses: docker/login-action@v3 |
| 178 | + with: |
| 179 | + username: ${{ vars.DOCKER_USERNAME }} |
| 180 | + password: ${{ secrets.DOCKER_PASSWORD }} |
| 181 | +``` |
| 182 | + |
| 183 | +This assumes `DOCKER_USERNAME` is configured as a |
| 184 | +[variable](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables), |
| 185 | +and `DOCKER_PASSWORD` as a |
| 186 | +[secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), |
| 187 | +in the GitHub repository, containing the username and password for your Docker |
| 188 | +account. |
| 189 | + |
| 190 | +## Build and push the image |
| 191 | + |
| 192 | +Finally, build the final production image and push it to your registry. This |
| 193 | +example also configures the action to generate SBOM and provenance |
| 194 | +[attestations](/manuals/build/metadata/attestations/_index.md) at this stage. |
| 195 | +SBOM (Software Bill of Materials) provides a detailed list of components |
| 196 | +included in your image, while provenance documents where, when, and how your |
| 197 | +image was built. Adding these attestations is a best practice for improving |
| 198 | +security and ensuring software supply chain integrity. |
| 199 | + |
| 200 | +```yaml |
| 201 | +- name: Build and push Docker image |
| 202 | + uses: docker/build-push-action@v6 |
| 203 | + with: |
| 204 | + push: ${{ github.event_name != 'pull_request' }} |
| 205 | + tags: ${{ steps.meta.outputs.tags }} |
| 206 | + labels: ${{ steps.meta.outputs.labels }} |
| 207 | + provenance: true |
| 208 | + sbom: true |
| 209 | +``` |
| 210 | + |
| 211 | +In this configuration: |
| 212 | + |
| 213 | +- `push: ${{ github.event_name != 'pull_request' }}` ensures that images are |
| 214 | + only pushed when the event is not a pull request. This way, the workflow |
| 215 | + builds and tests images for pull requests but only pushes images for commits |
| 216 | + to the main branch. |
| 217 | +- `provenance: true` and `sbom: true` enable the generation of provenance and |
| 218 | + SBOM attestations, enhancing the security and traceability of your images. |
| 219 | +- `tags` and `labels` use the outputs from the metadata action to apply |
| 220 | + consistent tagging and labeling. |
| 221 | + |
| 222 | +## Conclusion |
| 223 | + |
| 224 | +With all the steps outlined in the previous section, here's the full workflow |
| 225 | +configuration: |
| 226 | + |
| 227 | +```yaml |
| 228 | +name: Build and Push Docker Image |
| 229 | +
|
| 230 | +on: |
| 231 | + push: |
| 232 | + branches: |
| 233 | + - main |
| 234 | + pull_request: |
| 235 | +
|
| 236 | +jobs: |
| 237 | + build: |
| 238 | + runs-on: ubuntu-latest |
| 239 | + steps: |
| 240 | + - name: Test the app |
| 241 | + uses: docker/build-push-action@v6 |
| 242 | + with: |
| 243 | + target: tests |
| 244 | +
|
| 245 | + - name: Extract Docker image metadata |
| 246 | + id: meta |
| 247 | + uses: docker/metadata-action@v5 |
| 248 | + with: |
| 249 | + images: my-dockerhub-user/my-image |
| 250 | +
|
| 251 | + - name: Log in to Docker Hub |
| 252 | + uses: docker/login-action@v3 |
| 253 | + with: |
| 254 | + username: ${{ secrets.DOCKER_USERNAME }} |
| 255 | + password: ${{ secrets.DOCKER_PASSWORD }} |
| 256 | +
|
| 257 | + - name: Build and push Docker image |
| 258 | + uses: docker/build-push-action@v6 |
| 259 | + with: |
| 260 | + push: ${{ github.event_name != 'pull_request' }} |
| 261 | + tags: ${{ steps.meta.outputs.tags }} |
| 262 | + labels: ${{ steps.meta.outputs.labels }} |
| 263 | + provenance: true |
| 264 | + sbom: true |
| 265 | +``` |
| 266 | + |
| 267 | +### Further reading |
| 268 | + |
| 269 | +- Learn more about advanced configurations and examples in the [Docker Build |
| 270 | + GitHub Actions](/manuals/build/ci/github-actions/_index.md) section. |
| 271 | +- For more complex build setups, you may want to consider |
| 272 | + [Bake](/manuals/build/bake/_index.md). (See also the |
| 273 | + [Mastering Buildx Bake guide](/guides/bake/index.md).) |
| 274 | +- If your builds are slow or you're building multi-platform images, take a look |
| 275 | + at [Docker Build Cloud](/guides/docker-build-cloud/_index.md), a managed |
| 276 | + build service that easily integrates with both local and GitHub Actions |
| 277 | + workflows. |
0 commit comments