build: reworked ci/gha docs

Signed-off-by: David Karlsson <david.karlsson@docker.com>
This commit is contained in:
David Karlsson 2022-10-11 11:27:27 +02:00
parent 20d69349a3
commit f636cc21d3
20 changed files with 1511 additions and 1633 deletions

View File

@ -145,12 +145,6 @@ guides:
title: Create your own base image (advanced)
- path: /develop/scan-images/
title: Scan images
- sectiontitle: Set up CI/CD
section:
- path: /ci-cd/best-practices/
title: CI/CD Best practices
- path: /ci-cd/github-actions/
title: Configure GitHub Actions
- sectiontitle: Deploy your app to the cloud
section:
- path: /cloud/aci-integration/
@ -1583,6 +1577,16 @@ manuals:
title: Install Buildx
- path: /build/buildx/multiple-builders/
title: Using multiple builders
- sectiontitle: Continuous integration
section:
- path: /build/ci/
title: CI with Docker
- sectiontitle: GitHub Actions
section:
- path: /build/ci/github-actions/
title: Introduction
- path: /build/ci/github-actions/examples/
title: Examples
- path: /build/release-notes/
title: Release notes
- sectiontitle: Docker Compose

178
_includes/gha-tutorial.md Normal file
View File

@ -0,0 +1,178 @@
This tutorial walks you through the process of setting up and using Docker GitHub
Actions for building Docker images, and pushing images to Docker Hub. You will
complete the following steps:
1. Create a new repository on GitHub.
2. Define the GitHub Actions workflow.
3. Run the workflow.
To follow this tutorial, you need a Docker ID and a GitHub account.
### Step one: Create the repository
Create a GitHub repository and configure the Docker Hub secrets.
1. Create a new GitHub repository using
[this template repository](https://github.com/dvdksn/clockbox/generate).
The repository contains a simple Dockerfile, and nothing else. Feel free to
use another repository containing a working Dockerfile if you prefer.
2. Open the repository **Settings**, and go to **Secrets** > **Actions**.
3. Create a new secret named `DOCKER_HUB_USERNAME` and your Docker ID as value.
4. Create a new
[Personal Access Token (PAT)](https://docs.docker.com/docker-hub/access-tokens/#create-an-access-token)
for Docker Hub. You can name this token `clockboxci`.
5. Add the PAT as a second secret in your GitHub repository, with the name
`DOCKER_HUB_ACCESS_TOKEN`.
With your repository created, and secrets configured, you're now ready for
action!
### Step two: Set up the workflow
Set up your GitHub Actions workflow for building and pushing the image to Docker
Hub.
1. Go to your repository on GitHub and then select the **Actions** tab.
2. Select **set up a workflow yourself**.
This takes you to a page for creating a new GitHub actions workflow file in
your repository, under `.github/workflows/main.yml` by default.
3. In the editor window, copy and paste the following YAML configuration.
```yaml
name: ci
on:
push:
branches:
- "main"
jobs:
build:
runs-on: ubuntu-latest
```
- `name`: the name of this workflow.
- `on.push.branches`: specifies that this workflow should run on every push
event for the branches in the list.
- `jobs`: creates a job ID (`build`) and declares the type of machine that
the job should run on.
For more information about the YAML syntax used here, see
[Workflow syntax for GitHub Actions](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions){:
target="blank" rel="noopener"}.
### Step three: Define the workflow steps
Now the essentials: what steps to run, and in what order to run them.
{% raw %}
```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/clockbox:latest
```
{% endraw %}
The previous YAML snippet contains a sequence of steps that:
1. Checks out the repository on the build machine.
2. Signs in to Docker Hub, using the
[Docker Login](https://github.com/marketplace/actions/docker-login){:
target="blank" rel="noopener"} action and your Docker Hub credentials.
3. Creates a BuildKit builder instance using the
[Docker Setup Buildx](https://github.com/marketplace/actions/docker-setup-buildx){:
target="blank" rel="noopener"} action.
4. Builds the container image and pushes it to the Docker Hub repository, using
[Build and push Docker images](https://github.com/marketplace/actions/build-and-push-docker-images){:
target="blank" rel="noopener"}.
The `with` key lists a number of input parameters that configures the step:
- `context`: the build context.
- `file`: filepath to the Dockerfile.
- `push`: tells the action to upload the image to a registry after building
it.
- `tags`: tags that specify where to push the image.
Add these steps to your workflow file. The full workflow configuration should
look as follows:
{% raw %}
```yaml
name: ci
on:
push:
branches:
- "main"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/clockbox:latest
```
{% endraw %}
### Run the workflow
Save the workflow file and run the job.
1. Select **Start commit** and push the changes to the `main` branch.
After pushing the commit, the workflow starts automatically.
2. Go to the **Actions** tab. It displays the workflow.
Selecting the workflow shows you the breakdown of all the steps.
3. When the workflow is complete, go to your
[repositories on Docker Hub](https://hub.docker.com/repositories){:
target="blank" rel="noopener"}.
If you see the new repository in that list, it means the GitHub Actions
successfully pushed the image to Docker Hub!

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
---
title: Introduction to GitHub Actions
description: >
Docker maintains a set of official GitHub Actions for building Docker images.
keywords: github, actions, gha, ci, build, introduction, tutorial
redirect_from:
- /ci-cd/github-actions/
---
GitHub Actions is a popular CI/CD platform for automating your build, test, and
deployment pipeline. Docker provides a set of official GitHub Actions for you to
use in your workflows. These official actions are reusable, easy-to-use
components for building, annotating, and pushing images.
The following GitHub Actions are available:
- [Build and push Docker images](https://github.com/marketplace/actions/build-and-push-docker-images){:
target="blank" rel="noopener" class=""}: build and push Docker images with
BuildKit.
- [Docker Login](https://github.com/marketplace/actions/docker-login){:
target="blank" rel="noopener"}: sign in to a Docker registry.
- [Docker Setup Buildx](https://github.com/marketplace/actions/docker-setup-buildx){:
target="blank" rel="noopener"}: initiates a BuildKit builder
- [Docker Metadata action](https://github.com/marketplace/actions/docker-metadata-action){:
target="blank" rel="noopener"}: extracts metadata from Git reference and
GitHub events.
- [Docker Setup QEMU](https://github.com/marketplace/actions/docker-setup-qemu){:
target="blank" rel="noopener"}: installs [QEMU](https://github.com/qemu/qemu)
static binaries for multi-arch builds.
- [Docker Buildx Bake](https://github.com/marketplace/actions/docker-buildx-bake){:
target="blank" rel="noopener"}: enables using `docker buildx bake`.
Using Docker's actions provides an easy-to-use interface, while still allowing
flexibility for customizing build parameters.
## Get started with GitHub Actions
{% include gha-tutorial.md %}
## Next steps
This tutorial has shown you how to create a simple GitHub Actions workflow,
using the official Docker actions, to build and push an image to Docker Hub.
There are many more things you can do to customize your workflow to better suit
your needs. To learn more about some of the more advanced use cases, take a look
at the advanced examples, such as
[building multi-platform images](examples.md#multi-platform-images), or
[using cache storage backends](examples.md#cache).

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

63
build/ci/index.md Normal file
View File

@ -0,0 +1,63 @@
---
description: Overview of using Docker for continuous integration
keywords: ci, build
redirect_from:
- /ci-cd/best-practices/
title: Continuous integration with Docker
---
Continuous Integration (CI) is the part of the development process where you're
looking to get your code changes merged with the main branch of the project. At
this point, development teams run tests and builds to vet that the code changes
don't cause any unwanted or unexpected behaviors.
![Git branches about to get merged](./images/continuous-integration.svg){:
.invertible }
There are several uses for Docker at this stage of development, even if you
don't end up packaging your application as a container image.
## Docker as a build environment
Containers are reproducible, isolated environments that yield predictable
results. Building and testing your application in a Docker container makes it
easier to prevent unexpected behaviors from occurring. Using a Dockerfile, you
define the exact requirements for the build environment, including programming
runtimes, operating system, binaries, and more.
Using Docker to manage your build environment also eases maintenance. For
example, updating to a new version of a programming runtime can be as simple as
changing a tag or digest in a Dockerfile. No need to SSH into a pet VM to
manually reinstall a newer version and update the related configuration files.
Additionally, just as you expect third-party open source packages to be secure,
the same should go for your build environment. You can scan and index a builder
image, just like you would for any other containerized application.
The following links provide instructions for how you can get started using
Docker for building your applications in CI:
- [GitHub Actions](https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action){:
target="blank" rel="noopener" class=""}
- [GitLab](https://docs.gitlab.com/runner/executors/docker.html){:
target="blank" rel="noopener" class=""}
- [Circle CI](https://circleci.com/docs/using-docker/){: target="blank"
rel="noopener" class=""}
- [Render](https://render.com/docs/docker){: target="blank" rel="noopener"
class=""}
### Docker in Docker
You can also use a Dockerized build environment to build container images using
Docker. That is, your build environment runs inside a container which itself is
equipped to run Docker builds. This method is referred to as "Docker in Docker".
Docker provides an official [Docker image](https://hub.docker.com/_/docker) that
that you can use for this purpose.
## What's next
Docker maintains a set of official GitHub Actions that you can use to build,
annotate, and push container images on the GitHub Actions platform. See
[Introduction to GitHub Actions](./github-actions/index.md) to learn more and
get started.

View File

@ -1,46 +0,0 @@
---
description: Best practices for using Docker Hub for CI/CD
keywords: CI/CD, GitHub Actions,
title: Best practices for using Docker Hub for CI/CD
---
According to the [2020 Jetbrains developer survey](https://www.jetbrains.com/lp/devecosystem-2020/){:target="_blank" rel="noopener" class="_"} , 44% of developers are now using some form of continuous integration and deployment with Docker containers. We understand that a large number of developers have got this set up using Docker Hub as their container registry for part of their workflow. This guide contains some best practices for doing this and provides guidance on how to get started.
We have also heard feedback that given the changes [Docker introduced](https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/){:target="_blank" rel="noopener" class="_"} relating to network egress and the number of pulls for free users, that there are questions around the best way to use Docker Hub as part of CI/CD workflows without hitting these limits. This guide covers best practices that improve your experience and uses a sensible consumption of Docker Hub which mitigates the risk of hitting these limits, and contains tips on how to increase the limits depending on your use case.
## Inner and outer loops
To get started, one of the most important things when working with Docker and any CI/CD is to understand when you need to test with the CI, and when you can do this locally. At Docker, we think about how developers work in terms of their inner loop (code, build, run, test) and their outer loop (push changes, CI build, CI test, deployment).
![CI/CD inner and outer loop](images/inner-outer-loop.png)
Before you think about optimizing your CI/CD, it is important to think about your inner loop and how it relates to the outer loop (the CI). We know that most users don't prefer 'debugging through the CI. Therefore, it is better if your inner loop and outer loop are as similar as possible. We recommend that you run unit tests as part of your `docker build` command by adding a target for them in your Dockerfile. This way, as you are making changes and rebuilding locally, you can run the same unit tests you would run in the CI on your local machine using a simple command.
The blog post [Go development with Docker](https://www.docker.com/blog/tag/go-env-series/){:target="_blank" rel="noopener" class="_"} is a great example of how you can use tests in your Docker project and re-use them in the CI. This also creates a shorter feedback loop on issues and reduces the amount of pulls and builds your CI needs to do.
## Optimizing CI/CD deployments
Once you get into your actual outer loop and Docker Hub, there are a few things you can do to get the most of your CI and deliver the fastest Docker experience.
First and foremost, stay secure. When you are setting up your CI, ensure you are using a Docker Hub access token, rather than your password.
> **Note**
>
> You can create new access tokens from your [Security](https://hub.docker.com/settings/security){:target="_blank" rel="noopener" class="_"} page on Docker Hub.
Once you have created access tokens and have added it to a secrets store on your platform, you need to consider when to push and pull in your CI/CD, along with where from, depending on the change you are making.
The first thing you can do to reduce the build time and reduce your number of calls is make use of the **build cache** to reuse layers you have already pulled. You can do this on many platforms by using buildX (buildkits) caching functionality and whatever cache your platform provides. For example, see [Optimizing the GitHub Actions workflow using build cache](../github-actions#optimizing-the-workflow).
The other change you may want to make is only have your release images go to Docker Hub. This would mean setting up functions to push your PR images to a more local image store to be quickly pulled and tested, rather than promoting them all the way up to production.
## Next steps
We know there are a lot more tips and tricks for using Docker in CI.
However, we think these are some of the important things, considering the [Docker Hub rate limits](../docker-hub/download-rate-limit.md).
> **Note**
>
> If you are still experiencing issues with pull limits after you are authenticated, you can consider upgrading to a [Docker subscription](https://www.docker.com/pricing){:target="_blank" rel="noopener" class="_"}.
For information on how to configure GitHub Actions CI/CD pipeline, see [Configure GitHub Actions](github-actions.md).

View File

@ -1,288 +0,0 @@
---
description: Configure GitHub Actions
keywords: CI/CD, GitHub Actions,
title: Configure GitHub Actions
---
This page guides you through the process of setting up a GitHub Action CI/CD
pipeline with Docker. Before setting up a new pipeline, we recommend that you take a look at [Ben's blog](https://www.docker.com/blog/best-practices-for-using-docker-hub-for-ci-cd/){:target="_blank" rel="noopener" class="_"}
on CI/CD best practices.
This guide contains instructions on how to:
1. Use a sample Docker project as an example to configure GitHub Actions.
2. Set up the GitHub Actions workflow.
3. Optimize your workflow to reduce build time.
4. Push only specific versions to Docker Hub.
## Set up a Docker project
Let's get started. This guide uses a simple Docker project as an example. The
[SimpleWhaleDemo](https://github.com/usha-mandya/SimpleWhaleDemo){:target="_blank" rel="noopener" class="_"}
repository contains a Nginx alpine image. You can either clone this repository,
or use your own Docker project.
![SimpleWhaleDemo](images/simplewhaledemo.png){:width="500px"}
Before we start, ensure you can access [Docker Hub](https://hub.docker.com/)
from any workflows you create. To do this:
1. Add your Docker ID as a secret to GitHub. Navigate to your GitHub repository
and click **Settings** > **Secrets** > **New secret**.
2. Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID
as value.
3. Create a new Personal Access Token (PAT). To create a new token, go to
[Docker Hub Settings](https://hub.docker.com/settings/security) and then click
**New Access Token**.
4. Let's call this token **simplewhaleci**.
![New access token](images/github-access-token.png){:width="500px"}
5. Now, add this Personal Access Token (PAT) as a second secret into the GitHub
secrets UI with the name `DOCKER_HUB_ACCESS_TOKEN`.
![GitHub Secrets](images/github-secrets.png){:width="500px"}
## Set up the GitHub Actions workflow
In the previous section, we created a PAT and added it to GitHub to ensure we
can access Docker Hub from any workflow. Now, let's set up our GitHub Actions
workflow to build and store our images in Hub.
In this example, let us set the push flag to `true` as we also want to push.
We'll then add a tag to specify to always go to the latest version. Lastly,
we'll echo the image digest to see what was pushed.
To set up the workflow:
1. Go to your repository in GitHub and then click **Actions** > **New workflow**.
2. Click **set up a workflow yourself** and add the following content:
First, we will name this workflow:
{% raw %}
```yaml
name: ci
```
{% endraw %}
Then, we will choose when we run this workflow. In our example, we are going to
do it for every push against the master branch of our project:
{% raw %}
```yaml
on:
push:
branches:
- 'master'
```
{% endraw %}
> **Note**
>
> The branch name may be `main` or `master`. Verify the name of the branch for your repository and update the configuration accordingly.
Now, we need to specify what we actually want to happen within our workflow
(what jobs), we are going to add our build one and select that it runs on the
latest Ubuntu instances available:
{% raw %}
```yaml
jobs:
build:
runs-on: ubuntu-latest
```
{% endraw %}
Now, we can add the steps required:
* The first one checks-out our repository under `$GITHUB_WORKSPACE`, so our workflow
can access it.
* The second one will use our PAT and username to log into Docker Hub.
* The third will setup Docker Buildx to create the builder instance using a
BuildKit container under the hood.
{% raw %}
```yaml
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
```
{% endraw %}
Now, let the workflow run for the first time and then tweak the Dockerfile to
make sure the CI is running and pushing the new image changes:
![CI to Docker Hub](images/ci-to-hub.png){:width="500px"}
## Optimizing the workflow
Next, let's look at how we can optimize the GitHub Actions workflow through
build cache using the registry. This allows to reduce the build time as it
will not have to run instructions that have not been impacted by changes in
your Dockerfile or source code and also reduce number of pulls we complete
against Docker Hub.
In this example, we need to add some extra attributes to the build and push
step:
{% raw %}
```yaml
-
name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v3
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
cache-from: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:buildcache
cache-to: type=registry,ref=${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:buildcache,mode=max
```
{% endraw %}
As you can see, we are using the `type=registry` cache exporter to import/export
cache from a cache manifest or (special) image configuration. Here it will be
pushed as a specific tag named `buildcache` for our image build.
Now, run the workflow again and verify that it uses the build cache.
## Push tagged versions and handle pull requests
Earlier, we learnt how to set up a GitHub Actions workflow to a Docker project,
how to optimize the workflow by setting up cache. Let's now look at how we can
improve it further. We can do this by adding the ability to have tagged versions
behave differently to all commits to master. This means, only specific versions
are pushed, instead of every commit updating the latest version on Docker Hub.
You can consider this approach to have your commits pushed as an edge tag to
then use it in nightly tests. By doing this, you can always test the last
changes of your active branch while reserving your tagged versions for release
to Docker Hub.
First, let us modify our existing GitHub workflow to take into account pushed
tags and pull requests:
{% raw %}
```yaml
on:
push:
branches:
- 'master'
tags:
- 'v*'
```
{% endraw %}
This ensures that the CI will trigger your workflow on push events
(branch and tags). If we tag our commit with something like `v1.0.2`:
{% raw %}
```console
$ git tag -a v1.0.2
$ git push origin v1.0.2
```
{% endraw %}
Now, go to GitHub and check your Actions
![Push tagged version](images/push-tagged-version.png){:width="500px"}
Let's reuse our current workflow to also handle pull requests for testing
purpose but also push our image in the GitHub Container Registry.
First we have to handle pull request events:
{% raw %}
```yaml
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
```
{% endraw %}
To authenticate against the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry){:target="_blank" rel="noopener" class="_"},
use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow){:target="_blank" rel="noopener" class="_"}
for the best security and experience.
Now let's change the Docker Hub login with the GitHub Container Registry one:
{% raw %}
```yaml
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
{% endraw %}
Remember to change how the image is tagged. The following example keeps latest'
as the only tag. However, you can add any logic to this if you prefer:
{% raw %}
```yaml
tags: ghcr.io/<username>/simplewhale:latest
```
{% endraw %}
> **Note**: Replace `<username>` with the repository owner. We could use
> {% raw %}`${{ github.repository_owner }}`{% endraw %} but this value can be mixed-case, so it could
> fail as [repository name must be lowercase](https://github.com/docker/build-push-action/blob/master/TROUBLESHOOTING.md#repository-name-must-be-lowercase){:target="_blank" rel="noopener" class="_"}.
![Update tagged images](images/ghcr-logic.png){:width="500px"}
Now, we will have two different flows: one for our changes to master, and one
for our pushed tags. Next, we need to modify what we had before to ensure we are
pushing our PRs to the GitHub registry rather than to Docker Hub.
## Conclusion
In this guide, you have learnt how to set up GitHub Actions workflow to an
existing Docker project, optimize your workflow to improve build times and
reduce the number of pull requests, and finally, we learnt how to push only
specific versions to Docker Hub.
## Next steps
You can now consider setting up nightly builds, test your image before pushing
it, setting up secrets, share images between jobs or automatically handle
tags and OCI Image Format Specification labels generation.
To look at how you can do one of these, or to get a full example on how to set
up what we have accomplished today, check out [our advanced examples](https://github.com/docker/build-push-action/tree/master/docs/advanced){:target="_blank" rel="noopener" class="_"}
which runs you through this and more details.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

View File

@ -6,235 +6,9 @@ description: Learn how to Configure CI/CD for your application
{% include_relative nav.html selected="4" %}
This page guides you through the process of setting up a GitHub Action CI/CD pipeline with Docker containers. Before setting up a new pipeline, we recommend that you take a look at [Ben's blog](https://www.docker.com/blog/best-practices-for-using-docker-hub-for-ci-cd/){:target="_blank" rel="noopener" class="_"} on CI/CD best practices .
## Get started with GitHub Actions
This guide contains instructions on how to:
1. Use a sample Docker project as an example to configure GitHub Actions
2. Set up the GitHub Actions workflow
3. Optimize your workflow to reduce the number of pull requests and the total build time, and finally,
4. Push only specific versions to Docker Hub.
## Set up a Docker project
Lets get started. This guide uses a simple Docker project as an example. The [SimpleWhaleDemo](https://github.com/usha-mandya/SimpleWhaleDemo){:target="_blank" rel="noopener" class="_"} repository contains an Nginx alpine image. You can either clone this repository, or use your own Docker project.
![SimpleWhaleDemo](../../ci-cd/images/simplewhaledemo.png){:width="500px"}
Before we start, ensure you can access [Docker Hub](https://hub.docker.com/) from any workflows you create. To do this:
1. Add your Docker ID as a secret to GitHub. Navigate to your GitHub repository and click **Settings** > **Secrets** > **New secret**.
2. Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID as value.
3. Create a new Personal Access Token (PAT). To create a new token, go to [Docker Hub Settings](https://hub.docker.com/settings/security) and then click **New Access Token**.
4. Lets call this token **simplewhaleci**.
![New access token](../../ci-cd/images/github-access-token.png){:width="500px"}
5. Now, add this Personal Access Token (PAT) as a second secret into the GitHub secrets UI with the name `DOCKER_HUB_ACCESS_TOKEN`.
![GitHub Secrets](../../ci-cd/images/github-secrets.png){:width="500px"}
## Set up the GitHub Actions workflow
In the previous section, we created a PAT and added it to GitHub to ensure we can access Docker Hub from any workflow. Now, lets set up our GitHub Actions workflow to build and store our images in Hub. We can achieve this by creating two Docker actions:
1. The first action enables us to log in to Docker Hub using the secrets we stored in the GitHub Repository.
2. The second one is the build and push action.
In this example, let us set the push flag to `true` as we also want to push. Well then add a tag to specify to always go to the latest version. Lastly, well echo the image digest to see what was pushed.
To set up the workflow:
1. Go to your repository in GitHub and then click **Actions** > **New workflow**.
2. Click **set up a workflow yourself** and add the following content:
First, we will name this workflow:
```yaml
name: CI to Docker Hub
```
Then, we will choose when we run this workflow. In our example, we are going to do it for every push against the master branch of our project:
```yaml
on:
push:
branches: [ master ]
```
> **Note**
>
> The branch name may be `main` or `master`. Verify the name of the branch for your repository and update the configuration accordingly.
Now, we need to specify what we actually want to happen within our action (what jobs), we are going to add our build one and select that it runs on the latest Ubuntu instances available:
```yaml
jobs:
build:
runs-on: ubuntu-latest
```
Now, we can add the steps required. The first one checks-out our repository under `$GITHUB_WORKSPACE`, so our workflow can access it. The second is to use our PAT and username to log into Docker Hub. The third is the Builder, the action uses BuildKit under the hood through a simple Buildx action which we will also setup
{% raw %}
```yaml
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, let the workflow run for the first time and then tweak the Dockerfile to make sure the CI is running and pushing the new image changes:
![CI to Docker Hub](../../ci-cd/images/ci-to-hub.png){:width="500px"}
## Optimizing the workflow
Next, lets look at how we can optimize the GitHub Actions workflow through build cache. This has two main advantages:
1. Build cache reduces the build time as it will not have to re-download all of the images, and
2. It also reduces the number of pulls we complete against Docker Hub. We need to make use of GitHub cache to make use of this.
Let us set up a Builder with a build cache. First, we need to set up cache for the builder. In this example, let us add the path and keys to store this under using GitHub cache for this.
{% raw %}
```yaml
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
```
{% endraw %}
And lastly, after adding the builder and build cache snippets to the top of the Actions file, we need to add some extra attributes to the build and push step. This involves:
- Setting up the builder to use the output of the buildx step, and then
- Using the cache we set up earlier for it to store to and to retrieve
{% raw %}
```yaml
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, run the workflow again and verify that it uses the build cache.
## Push tagged versions to Docker Hub
Earlier, we learnt how to set up a GitHub Actions workflow to a Docker project, how to optimize the workflow by setting up a builder with build cache. Lets now look at how we can improve it further. We can do this by adding the ability to have tagged versions behave differently to all commits to master. This means, only specific versions are pushed, instead of every commit updating the latest version on Docker Hub.
You can consider this approach to have your commits go to a local registry to then use in nightly tests. By doing this, you can always test what is latest while reserving your tagged versions for release to Docker Hub.
This involves two steps:
1. Modifying the GitHub workflow to only push commits with specific tags to Docker Hub
2. Setting up a GitHub Actions file to store the latest commit as an image in the GitHub registry
First, let us modify our existing GitHub workflow to only push to Hub if theres a particular tag. For example:
{% raw %}
```yaml
on:
push:
tags:
- "v*.*.*"
```
{% endraw %}
This ensures that the main CI will only trigger if we tag our commits with `Vn.n.n.` Lets test this. For example, run the following command:
```console
$ git tag -a v1.0.2
$ git push origin v1.0.2
```
Now, go to GitHub and check your Actions
![Push tagged version](../../ci-cd/images/push-tagged-version.png){:width="500px"}
Now, lets set up a second GitHub action file to store our latest commit as an image in the GitHub registry. You may want to do this to:
1. Run your nightly tests or recurring tests, or
2. To share work in progress images with colleagues.
Lets clone our previous GitHub action and add back in our previous logic for all pushes. This will mean we have two workflow files, our previous one and our new one we will now work on.
Next, change your Docker Hub login to a GitHub container registry login:
{% raw %}
```yaml
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
{% endraw %}
To authenticate against the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry), use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) for the best security and experience.
You may need to [manage write and read access of GitHub Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio) for repositories in the container settings.
You can also use a [personal access token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the [appropriate scopes](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry).
Remember to change how the image is tagged. The following example keeps latest as the only tag. However, you can add any logic to this if you prefer:
{% raw %}
```yaml
tags: ghcr.io/${{ github.repository_owner }}/simplewhale:latest
```
{% endraw %}
![Update tagged images](../../ci-cd/images/ghcr-logic.png){:width="500px"}
Now, we will have two different flows: one for our changes to master, and one for our pull requests. Next, we need to modify what we had before to ensure we are pushing our PRs to the GitHub registry rather than to Docker Hub.
{% include gha-tutorial.md %}
## Next steps

View File

@ -8,331 +8,9 @@ redirect_from:
{% include_relative nav.html selected="5" %}
This page guides you through the process of setting up a GitHub Action CI/CD pipeline with Docker containers. Before setting up a new pipeline, we recommend that you take a look at [Ben's blog](https://www.docker.com/blog/best-practices-for-using-docker-hub-for-ci-cd/){:target="_blank" rel="noopener" class="_"} on CI/CD best practices.
## Get started with GitHub Actions
This guide contains instructions on how to:
1. Set up continuous integration (CI) pipeline using GitHub Actions;
2. Enable Docker Hub access for continuous deployment (CD) tools;
3. Optimize your GitHub Actions-based CI/CD pipeline to reduce the number of pull requests and the total build time; and
4. Release only specific versions of your application to Docker Hub.
> **Note**
>
> Before we begin, it must be said that Continuous Integration (CI) and Continuous Deployment (CD) each is a _huge_ subject, with many different approaches and opinions. The approach chosen in this guide is optimised for pedagogical clarity and simplicity and is not meant to be the One True Way to test and release software.
## Choose a sample project
Lets get started. This guide uses a simple Go project as an example. In fact, it is the same project we got acquainted with in [Build Images](build-images.md) part of this guide. The [olliefr/docker-gs-ping](https://github.com/olliefr/docker-gs-ping){:target="_blank" rel="noopener" class="_"} repository contains the full source code and the `Dockerfile`. You can either fork it or to follow along and set up one of your own Go projects in a fashion described in this section.
Thus, as long as you have a GitHub repo with a project and a `Dockerfile`, you can complete this part of the tutorial.
## Enable access to Docker Hub
The [Docker Hub](https://hub.docker.com/) is a hosted repository service provided by Docker for finding and sharing container images.
Before we can publish our Docker image to Docker Hub, we must grant GitHub Actions access to Docker Hub API.
To set up the access to Docker Hub API:
1. Create a new Personal Access Token (PAT) for Docker Hub.
* Go to the [Docker Hub Account Settings](https://hub.docker.com/settings/security) and then click **New Access Token**.
* Let's call this token `docker-gs-ping-ci`. Input the name and click **Create**.
* Copy the token value, we'll need it in a second.
2. Add your Docker ID and PAT as secrets to your GitHub repo.
* Navigate to your GitHub repository and click **Settings** > **Secrets** > **New secret**.
* Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID as value.
* Create a new secret with the name `DOCKER_HUB_ACCESS_TOKEN` and use the token value from the step (1).
Your GitHub repository **Secrets** section would look like the following.
![GitHub Secrets](../../ci-cd/images/github-secrets.png){:width="500px"}
Now it will be possible to refer to these two variables from our workflows. This will open up an opportunity to publish our image to Docker Hub.
## Set up the CI workflow
In the previous section, we created a PAT and added it to GitHub to ensure we can access Docker Hub from any GitHub Actions workflow. But before setting out to build the images for releasing our software, let's build a CI pipeline to run the tests first.
To set up the workflow:
1. Go to your repository in GitHub and then click **Actions** > **New workflow**.
2. Click **set up a workflow yourself** and update the starter template to match the following:
First, we will name this workflow:
{% raw %}
```yaml
name: Run CI
```
{% endraw %}
Then, we will choose when we run this workflow. In our example, we are going to do it for every push against the main branch of our project:
{% raw %}
```yaml
on:
push:
branches: [ main ]
workflow_dispatch:
```
{% endraw %}
The `workflow_dispatch` is optional. It enables to run this workflow manually from the Actions tab.
Now, we need to specify what we actually want to happen within our workflow. A workflow run is made up of one or more _jobs_ that can run sequentially or in parallel.
The first job we would like to set up is the one to build and run our tests. Let it be run on the latest Ubuntu instance:
{% raw %}
```yaml
jobs:
build-and-test:
runs-on: ubuntu-latest
```
{% endraw %}
A job is a sequence of _steps_. For this simple CI pipeline we would like to:
1. Set up Go compiler environment.
2. Check out our code from its GitHub repository.
3. Fetch Go modules used by our application.
4. (Optional) Build the binary for our application.
5. Build the Docker Image for our application.
6. Run the functional tests for our application against that Docker image.
Building the binary in step 4 is actually optional. It is a "smoke test". We don't want to be building a Docker image and attempting functional testing if our application does not even compile. If we had "unit tests" or some other small tests, we would run them between steps 4 and 5 as well.
The following sequence of `steps` achieves the goals we just set.
{% raw %}
```yaml
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16.4
- name: Checkout code
uses: actions/checkout@v2
- name: Fetch required Go modules
run: go mod download
- name: Build
run: go build -v ./...
- name: Build Docker image
uses: docker/build-push-action@v2
with:
push: false
tags: ${{ github.event.repository.name }}:latest, ${{ github.repository }}:latest
- name: Run functional tests
run: go test -v ./...
```
{% endraw %}
As is usual with YAML files, be aware of indentation. The complete workflow file for reference is available in the project's repo, under the name of `.github/workflows/ci.yml`.
This should be enough to test our approach to CI. Change the workflow file name from `main.yml` to `ci.yml` and press **Start commit** button. Fill out the commit details in your preferred style and press **Commit new file**. GitHub Actions are saved as YAML files in `.github/workflows` directory and GitHub web interface would do that for us.
Select **Actions** from the navigation bar for your repository. Since we've enabled `workflow_dispatch` option in our Action, GitHub will have started it already. If not, select "CI/CD to Docker Hub" action on the left, and then press **Run workflow** button on the right to start the workflow.
![GitHub Secrets](images/runworkflow.png){:width="500px"}
Should the run fail, you can click on the failing entry to see the logs and amend the workflow YAML file accordingly.
## Set up the CD workflow
Now, lets create a GitHub Actions workflow to build and store the image for our application in Docker Hub. We can achieve this by creating two Docker actions:
1. The first action enables us to log in to Docker Hub using the secrets we stored in the GitHub Repository settings.
2. The second one is the build and push action.
In this example, let us set the push flag to `true` as we also want to push. Well then add a tag to specify to always go to the latest version. Lastly, well echo the image digest to see what was pushed.
Now, we can add the steps required. Start a blank new workflow, just as we did before. Let's give it a file name of `release.yml` and amend the template body to match the following.
{% raw %}
```yaml
name: Release to Docker Hub
on:
push:
tags:
- "*.*.*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.16.4
- name: Checkout code
uses: actions/checkout@v2
- name: Fetch required Go modules
run: go mod download
- name: Build
run: go build -v ./...
- name: Build and push Docker image
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ github.event.repository.name }}:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
This workflow is similar to the CI workflow, with the following changes:
* This workflow is only triggered when a git tag of the format `*.*.*` is pushed to the repo. The tag meant to be a semantic version, such as `3.5.0` or `0.0.1`.
* The very first step is to login into Docker Hub using the two secrets that we had saved in the repository settings previously.
* The _build and push_ step now has `push: true` and since we had logged into Docker Hub this will result in the latest image being published.
* The image digest step prints out the image metadata to the log.
Let's save this workflow and check the _Actions_ page for the repository on GitHub. Unlike the CI workflow, this new workflow cannot be triggered manually - this is how we set it up. So, in order to test it, we have to tag some commit. Let's tag the `HEAD` of the `main` branch:
```console
$ git tag -a 0.0.1 -m "Test release workflow"
$ git push --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 169 bytes | 169.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To https://github.com/olliefr/docker-gs-ping.git
* [new tag] 0.0.1 -> 0.0.1
```
This means our tag was successfully pushed to the main repo. If we switch to the GitHub UI, we would see that the workflow has already been triggered:
![GitHub Secrets](images/triggerontag.png){:width="500px"}
Plot twist! Despite having explained how to add secrets to the repository, we had forgotten to do it ourselves. And the workflow run results in error:
![GitHub Secrets](images/loginerror.png){:width="500px"}
That's easy to fix. We follow our own guide and add the secrets to the repository settings. But how do we re-run the workflow? We need to remove the tag and reapply it.
To remove the tag on the `remote`:
```console
$ git push origin :refs/tags/0.0.1
To https://github.com/olliefr/docker-gs-ping.git
- [deleted] 0.0.1
```
And to re-apply it locally and push:
```console
$ git tag -fa 0.0.1 -m "Test release workflow"
Updated tag '0.0.1' (was d7d3edc)
$ git push origin --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 170 bytes | 170.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To https://github.com/olliefr/docker-gs-ping.git
* [new tag] 0.0.1 -> 0.0.1
```
And the workflow is triggered again. This time it completes without issues:
![GitHub Secrets](images/cdsuccess.png){:width="500px"}
Since the image we've just pushed to Docker Hub is public, now it can be pulled by anyone, from anywhere:
```console
$ docker pull olliefr/docker-gs-ping
Using default tag: latest
latest: Pulling from olliefr/docker-gs-ping
540db60ca938: Already exists
adcc1eea9eea: Already exists
4c4ab2625f07: Already exists
c5e7595549f7: Pull complete
3df88182f7ac: Pull complete
56c9181b0012: Pull complete
04b91de9e9ed: Pull complete
7a1dde643d3d: Pull complete
f815a8b426ad: Pull complete
7a6b1ee48c34: Pull complete
ca1a2b73aa81: Pull complete
Digest: sha256:81bedd562952757322a07a26634b01e3916db375cc695843124f79641e433029
Status: Downloaded newer image for olliefr/docker-gs-ping:latest
docker.io/olliefr/docker-gs-ping:latest
```
We've just build a simple (maybe even naive) CI/CD workflow. There are many ways in which it can be improved. We'll look at some of these ways in the next section.
## Optimizing the workflow
Next, lets look at how we can optimize the GitHub Actions workflow through build cache. This has two main advantages:
1. Build cache reduces the build time as it will not have to re-download all of the images, and
2. It also reduces the number of pulls we complete against Docker Hub. We need to make use of GitHub cache to make use of this.
Let us set up a Builder with a build cache. First, we need to set up the builder, and then to configure the cache. In this example, let us add the path and keys to store this under using GitHub cache for this.
{% raw %}
```yaml
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
```
{% endraw %}
And lastly, after adding the builder and build cache snippets to the top of the Actions file, we need to add some extra attributes to the build and push step. This involves:
* Setting up the builder to use the output of the buildx step, and then
* Using the cache we set up earlier for it to store to and to retrieve
{% raw %}
```yaml
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
push: false
load: true
tags: ${{ github.event.repository.name }}:latest, ${{ github.repository }}:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
```
{% endraw %}
Now, run the CI workflow again and verify that it uses the build cache by checking the workflow log.
## Wrap up
GitHub Actions are an immensely powerful way to automate your CI and CD pipelines, and [there is a lot of documentation](https://docs.github.com/en/actions) to aid you in that mission. It is not, however, the _only_ way to integrate containers into your workflow. The aim of this section was to show you some of the basic things that are possible. There is a multitude of CI and CD tools on the market and you are very welcome to investigate what integration with the container ecosystem they provide. Well defined, automated CI pipelines can save you and your team a lot of effort.
{% include gha-tutorial.md %}
## Next steps

View File

@ -6,292 +6,9 @@ description: Learn how to Configure CI/CD for your application
{% include_relative nav.html selected="5" %}
This page guides you through the process of setting up a GitHub Action CI/CD pipeline with Docker containers. Before setting up a new pipeline, we recommend that you take a look at [Ben's blog](https://www.docker.com/blog/best-practices-for-using-docker-hub-for-ci-cd/){:target="_blank" rel="noopener" class="_"} on CI/CD best practices .
## Get started with GitHub Actions
This guide contains instructions on how to:
1. Use a sample Docker project as an example to configure GitHub Actions
2. Set up the GitHub Actions workflow
3. Optimize your workflow to reduce the number of pull requests and the total build time
4. Push only specific versions to Docker Hub
5. Optimize your image using multi-stage builds
## Set up a Docker project
Lets get started. This guide uses a simple Docker project as an example. The [SimpleWhaleDemo](https://github.com/usha-mandya/SimpleWhaleDemo){:target="_blank" rel="noopener" class="_"} repository contains an Nginx alpine image. You can either clone this repository, or use your own Docker project.
![SimpleWhaleDemo](../../ci-cd/images/simplewhaledemo.png){:width="500px"}
Before we start, ensure you can access [Docker Hub](https://hub.docker.com/) from any workflows you create. To do this:
1. Add your Docker ID as a secret to GitHub. Navigate to your GitHub repository and click **Settings** > **Secrets** > **New secret**.
2. Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID as value.
3. Create a new Personal Access Token (PAT). To create a new token, go to [Docker Hub Settings](https://hub.docker.com/settings/security) and then click **New Access Token**.
4. Lets call this token **simplewhaleci**.
![New access token](../../ci-cd/images/github-access-token.png){:width="500px"}
5. Now, add this Personal Access Token (PAT) as a second secret into the GitHub secrets UI with the name `DOCKER_HUB_ACCESS_TOKEN`.
![GitHub Secrets](../../ci-cd/images/github-secrets.png){:width="500px"}
## Set up the GitHub Actions workflow
In the previous section, we created a PAT and added it to GitHub to ensure we can access Docker Hub from any workflow. Now, lets set up our GitHub Actions workflow to build and store our images in Hub. We can achieve this by creating two Docker actions:
1. The first action enables us to log in to Docker Hub using the secrets we stored in the GitHub Repository.
2. The second one is the build and push action.
In this example, let us set the push flag to `true` as we also want to push. Well then add a tag to specify to always go to the latest version. Lastly, well echo the image digest to see what was pushed.
To set up the workflow:
1. Go to your repository in GitHub and then click **Actions** > **New workflow**.
2. Click **set up a workflow yourself** and add the following content:
First, we will name this workflow:
```yaml
name: CI to Docker Hub
```
Then, we will choose when we run this workflow. In our example, we are going to do it for every push against the master branch of our project:
```yaml
on:
push:
branches: [ master ]
```
> **Note**
>
> The branch name may be `main` or `master`. Verify the name of the branch for your repository and update the configuration accordingly.
Now, we need to specify what we actually want to happen within our action (what jobs), we are going to add our build one and select that it runs on the latest Ubuntu instances available:
```yaml
jobs:
build:
runs-on: ubuntu-latest
```
Now, we can add the steps required. The first one checks-out our repository under `$GITHUB_WORKSPACE`, so our workflow can access it. The second is to use our PAT and username to log into Docker Hub. The third is the Builder, the action uses BuildKit under the hood through a simple Buildx action which we will also setup
{% raw %}
```yaml
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, let the workflow run for the first time and then tweak the Dockerfile to make sure the CI is running and pushing the new image changes:
![CI to Docker Hub](../../ci-cd/images/ci-to-hub.png){:width="500px"}
## Optimize the workflow
Next, lets look at how we can optimize the GitHub Actions workflow through build cache. This has two main advantages:
1. Build cache reduces the build time as it will not have to re-download all of the images, and
2. It also reduces the number of pulls we complete against Docker Hub. We need to make use of GitHub cache to make use of this.
Let us set up a Builder with a build cache. First, we need to set up cache for the builder. In this example, let us add the path and keys to store this under using GitHub cache for this.
{% raw %}
```yaml
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
```
{% endraw %}
And lastly, after adding the builder and build cache snippets to the top of the Actions file, we need to add some extra attributes to the build and push step. This involves:
Setting up the builder to use the output of the buildx step, and then
Using the cache we set up earlier for it to store to and to retrieve
{% raw %}
```yaml
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, run the workflow again and verify that it uses the build cache.
## Push tagged versions to Docker Hub
Earlier, we learnt how to set up a GitHub Actions workflow to a Docker project, how to optimize the workflow by setting up a builder with build cache. Lets now look at how we can improve it further. We can do this by adding the ability to have tagged versions behave differently to all commits to master. This means, only specific versions are pushed, instead of every commit updating the latest version on Docker Hub.
You can consider this approach to have your commits go to a local registry to then use in nightly tests. By doing this, you can always test what is latest while reserving your tagged versions for release to Docker Hub.
This involves two steps:
1. Modifying the GitHub workflow to only push commits with specific tags to Docker Hub
2. Setting up a GitHub Actions file to store the latest commit as an image in the GitHub registry
First, let us modify our existing GitHub workflow to only push to Hub if theres a particular tag. For example:
{% raw %}
```yaml
on:
push:
tags:
- "v*.*.*"
```
{% endraw %}
This ensures that the main CI will only trigger if we tag our commits with `V.n.n.n.` Lets test this. For example, run the following command:
```console
$ git tag -a v1.0.2
$ git push origin v1.0.2
```
Now, go to GitHub and check your Actions
![Push tagged version](../../ci-cd/images/push-tagged-version.png){:width="500px"}
Now, lets set up a second GitHub action file to store our latest commit as an image in the GitHub registry. You may want to do this to:
1. Run your nightly tests or recurring tests, or
2. To share work in progress images with colleagues.
Lets clone our previous GitHub action and add back in our previous logic for all pushes. This will mean we have two workflow files, our previous one and our new one we will now work on.
Next, change your Docker Hub login to a GitHub container registry login:
{% raw %}
```yaml
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
{% endraw %}
To authenticate against the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry), use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) for the best security and experience.
You may need to [manage write and read access of GitHub Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio) for repositories in the container settings.
You can also use a [personal access token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the [appropriate scopes](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry).
Remember to change how the image is tagged. The following example keeps latest as the only tag. However, you can add any logic to this if you prefer:
{% raw %}
```yaml
tags: ghcr.io/${{ github.repository_owner }}/simplewhale:latest
```
{% endraw %}
![Update tagged images](../../ci-cd/images/ghcr-logic.png){:width="500px"}
Now, we will have two different flows: one for our changes to master, and one for our pull requests. Next, we need to modify what we had before to ensure we are pushing our PRs to the GitHub registry rather than to Docker Hub.
## Optimize your image using multi-stage builds
Now, lets take a look at the Dockerfile and see how we can optimize it to work in development, as well as get smaller images to run containers in production. As this is the last step in the file, it will be used by default to build the image when you run a docker build without specifying the target:
```console
$ docker build --tag java-docker .
docker build --tag java-docker .
[+] Building 1.2s (15/15) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/openjdk:11-jre-slim
=> [internal] load metadata for docker.io/library/openjdk:16-alpine3.13
=> [internal] load build context
=> => transferring context: 11.48kB
=> [production 1/2] FROM docker.io/library/openjdk:11-jre-slim@sha256:85795599f4c765182c414a1eb4e272841e18e2f267ce5010ea6a266f7f26e7f6
=> [base 1/6] FROM docker.io/library/openjdk:16-alpine3.13@sha256:49d822f4fa4deb5f9d0201ffeec9f4d113bcb4e7e49bd6bc063d3ba93aacbcae
=> CACHED [base 2/6] WORKDIR /app
=> CACHED [base 3/6] COPY .mvn/ .mvn
=> CACHED [base 4/6] COPY mvnw pom.xml ./
=> CACHED [base 5/6] RUN ./mvnw dependency:go-offline
=> CACHED [base 6/6] COPY src ./src
=> CACHED [build 1/1] RUN ./mvnw package
=> CACHED [production 2/2] COPY --from=build /app/target/spring-petclinic-*.jar /spring-petclinic.jar
=> exporting to image
=> => exporting layers
=> => writing image sha256:c17469b9e2f30537060f48bbe5d9d22003dd35edef7092348824a2438101ab3a
=> => naming to docker.io/library/java-docker
```
The second interesting point is that this step doesnt take the base target or a JDK image as reference. Instead, it uses a Java Runtime Environment image.
Note that you dont need a large image with all the development dependencies to run your application in production. Limiting the number of dependencies in production images can significantly limit the attack surface.
```dockerfile
FROM openjdk:11-jre-slim as production
EXPOSE 8080
COPY --from=build /app/target/spring-petclinic-*.jar /spring-petclinic.jar
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/spring-petclinic.jar"]
```
The container will also automatically expose its 8080 port and copies the Java Archive produced in build step to use it at container startup.
The production image built in this way only contains a runtime environment with the final application archive, just what you need to start your Spring Pet Clinic application.
```console
$ docker build --tag java-docker:jdk . --target development
$ docker build --tag java-docker:jre .
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
java-docker jre c17469b9e2f3 3 hours ago 270MB
java-docker jdk 4c15436d8ab7 5 hours ago 567MB
```
{% include gha-tutorial.md %}
## Next steps

View File

@ -6,235 +6,9 @@ description: Learn how to develop your application locally.
{% include_relative nav.html selected="5" %}
This page guides you through the process of setting up a GitHub Action CI/CD pipeline with Docker containers. Before setting up a new pipeline, we recommend that you take a look at [Ben's blog](https://www.docker.com/blog/best-practices-for-using-docker-hub-for-ci-cd/){:target="_blank" rel="noopener" class="_"} on CI/CD best practices .
## Get started with GitHub Actions
This guide contains instructions on how to:
1. Use a sample Docker project as an example to configure GitHub Actions
2. Set up the GitHub Actions workflow
3. Optimize your workflow to reduce the number of pull requests and the total build time, and finally,
4. Push only specific versions to Docker Hub.
## Set up a Docker project
Lets get started. This guide uses a simple Docker project as an example. The [SimpleWhaleDemo](https://github.com/usha-mandya/SimpleWhaleDemo){:target="_blank" rel="noopener" class="_"} repository contains an Nginx alpine image. You can either clone this repository, or use your own Docker project.
![SimpleWhaleDemo](../../ci-cd/images/simplewhaledemo.png){:width="500px"}
Before we start, ensure you can access [Docker Hub](https://hub.docker.com/) from any workflows you create. To do this:
1. Add your Docker ID as a secret to GitHub. Navigate to your GitHub repository and click **Settings** > **Secrets** > **New secret**.
2. Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID as value.
3. Create a new Personal Access Token (PAT). To create a new token, go to [Docker Hub Settings](https://hub.docker.com/settings/security) and then click **New Access Token**.
4. Lets call this token **simplewhaleci**.
![New access token](../../ci-cd/images/github-access-token.png){:width="500px"}
5. Now, add this Personal Access Token (PAT) as a second secret into the GitHub secrets UI with the name `DOCKER_HUB_ACCESS_TOKEN`.
![GitHub Secrets](../../ci-cd/images/github-secrets.png){:width="500px"}
## Set up the GitHub Actions workflow
In the previous section, we created a PAT and added it to GitHub to ensure we can access Docker Hub from any workflow. Now, lets set up our GitHub Actions workflow to build and store our images in Hub. We can achieve this by creating two Docker actions in the YAML file below:
1. The first action enables us to log in to Docker Hub using the secrets we stored in the GitHub Repository.
2. The second one is the build and push action.
In this example, let us set the push flag to `true` as we also want to push. Well then add a tag to specify to always go to the latest version. Lastly, well echo the image digest to see what was pushed.
To set up the workflow:
1. Go to your repository in GitHub and then click **Actions** > **New workflow**.
2. Click **set up a workflow yourself** and add the following content:
First, we will name this workflow:
```yaml
name: CI to Docker Hub
```
Then, we will choose when we run this workflow. In our example, we are going to do it for every push against the master branch of our project:
```yaml
on:
push:
branches: [ master ]
```
> **Note**
>
> The branch name may be `main` or `master`. Verify the name of the branch for your repository and update the configuration accordingly.
Now, we need to specify what we actually want to happen within our action (what jobs), we are going to add our build one and select that it runs on the latest Ubuntu instances available:
```yaml
jobs:
build:
runs-on: ubuntu-latest
```
Now, we can add the steps required. The first one checks-out our repository under `$GITHUB_WORKSPACE`, so our workflow can access it. The second is to use our PAT and username to log into Docker Hub. The third is the Builder, the action uses BuildKit under the hood through a simple Buildx action which we will also setup
{% raw %}
```yaml
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, let the workflow run for the first time and then tweak the Dockerfile to make sure the CI is running and pushing the new image changes:
![CI to Docker Hub](../../ci-cd/images/ci-to-hub.png){:width="500px"}
## Optimizing the workflow
Next, lets look at how we can optimize the GitHub Actions workflow through build cache. This has two main advantages:
1. Build cache reduces the build time as it will not have to re-download all of the images, and
2. It also reduces the number of pulls we complete against Docker Hub. We need to make use of GitHub cache to make use of this.
Let us set up a Builder with a build cache. First, we need to set up cache for the builder. In this example, let us add the path and keys to store this under using GitHub cache for this.
{% raw %}
```yaml
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
```
{% endraw %}
And lastly, after adding the builder and build cache snippets to the top of the Actions file, we need to add some extra attributes to the build and push step. This involves:
Setting up the builder to use the output of the buildx step, and then
Using the cache we set up earlier for it to store to and to retrieve
{% raw %}
```yaml
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, run the workflow again and verify that it uses the build cache.
## Push tagged versions to Docker Hub
Earlier, we learnt how to set up a GitHub Actions workflow to a Docker project, how to optimize the workflow by setting up a builder with build cache. Lets now look at how we can improve it further. We can do this by adding the ability to have tagged versions behave differently to all commits to master. This means, only specific versions are pushed, instead of every commit updating the latest version on Docker Hub.
You can consider this approach to have your commits go to a local registry to then use in nightly tests. By doing this, you can always test what is latest while reserving your tagged versions for release to Docker Hub.
This involves two steps:
1. Modifying the GitHub workflow to only push commits with specific tags to Docker Hub
2. Setting up a GitHub Actions file to store the latest commit as an image in the GitHub registry
First, let us modify our existing GitHub workflow to only push to Hub if theres a particular tag. For example:
{% raw %}
```yaml
on:
push:
tags:
- "v*.*.*"
```
{% endraw %}
This ensures that the main CI will only trigger if we tag our commits with `V.n.n.n.` Lets test this. For example, run the following command:
```console
$ git tag -a v1.0.2
$ git push origin v1.0.2
```
Now, go to GitHub and check your Actions
![Push tagged version](../../ci-cd/images/push-tagged-version.png){:width="500px"}
Now, lets set up a second GitHub action file to store our latest commit as an image in the GitHub registry. You may want to do this to:
1. Run your nightly tests or recurring tests, or
2. To share work in progress images with colleagues.
Lets clone our previous GitHub action and add back in our previous logic for all pushes. This will mean we have two workflow files, our previous one and our new one we will now work on.
Next, change your Docker Hub login to a GitHub container registry login:
{% raw %}
```yaml
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
{% endraw %}
To authenticate against the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry), use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) for the best security and experience.
You may need to [manage write and read access of GitHub Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio) for repositories in the container settings.
You can also use a [personal access token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the [appropriate scopes](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry).
Remember to change how the image is tagged. The following example keeps latest as the only tag. However, you can add any logic to this if you prefer:
{% raw %}
```yaml
tags: ghcr.io/${{ github.repository_owner }}/simplewhale:latest
```
{% endraw %}
![Update tagged images](../../ci-cd/images/ghcr-logic.png){:width="500px"}
Now, we will have two different flows: one for our changes to master, and one for our pull requests. Next, we need to modify what we had before to ensure we are pushing our PRs to the GitHub registry rather than to Docker Hub.
{% include gha-tutorial.md %}
## Next steps

View File

@ -6,235 +6,9 @@ description: Learn how to Configure CI/CD for your application
{% include_relative nav.html selected="4" %}
This page guides you through the process of setting up a GitHub Action CI/CD pipeline with Docker containers. Before setting up a new pipeline, we recommend that you take a look at [Ben's blog](https://www.docker.com/blog/best-practices-for-using-docker-hub-for-ci-cd/){:target="_blank" rel="noopener" class="_"} on CI/CD best practices.
## Get started with GitHub Actions
This guide contains instructions on how to:
1. Use a sample Docker project as an example to configure GitHub Actions
2. Set up the GitHub Actions workflow
3. Optimize your workflow to reduce the number of pull requests and the total build time, and finally,
4. Push only specific versions to Docker Hub.
## Set up a Docker project
Lets get started. This guide uses a simple Docker project as an example. The [SimpleWhaleDemo](https://github.com/usha-mandya/SimpleWhaleDemo){:target="_blank" rel="noopener" class="_"} repository contains an Nginx alpine image. You can either clone this repository, or use your own Docker project.
![SimpleWhaleDemo](../../ci-cd/images/simplewhaledemo.png){:width="500px"}
Before we start, ensure you can access [Docker Hub](https://hub.docker.com/) from any workflows you create. To do this:
1. Add your Docker ID as a secret to GitHub. Navigate to your GitHub repository and click **Settings** > **Secrets** > **New secret**.
2. Create a new secret with the name `DOCKER_HUB_USERNAME` and your Docker ID as value.
3. Create a new Personal Access Token (PAT). To create a new token, go to [Docker Hub Settings](https://hub.docker.com/settings/security) and then click **New Access Token**.
4. Lets call this token **simplewhaleci**.
![New access token](../../ci-cd/images/github-access-token.png){:width="500px"}
5. Now, add this Personal Access Token (PAT) as a second secret into the GitHub secrets UI with the name `DOCKER_HUB_ACCESS_TOKEN`.
![GitHub Secrets](../../ci-cd/images/github-secrets.png){:width="500px"}
## Set up the GitHub Actions workflow
In the previous section, we created a PAT and added it to GitHub to ensure we can access Docker Hub from any workflow. Now, lets set up our GitHub Actions workflow to build and store our images in Hub. We can achieve this by creating two Docker actions:
1. The first action enables us to log in to Docker Hub using the secrets we stored in the GitHub Repository.
2. The second one is the build and push action.
In this example, let us set the push flag to `true` as we also want to push. Well then add a tag to specify to always go to the latest version. Lastly, well echo the image digest to see what was pushed.
To set up the workflow:
1. Go to your repository in GitHub and then click **Actions** > **New workflow**.
2. Click **set up a workflow yourself** and add the following content:
First, we will name this workflow:
```yaml
name: CI to Docker Hub
```
Then, we will choose when we run this workflow. In our example, we are going to do it for every push against the master branch of our project:
```yaml
on:
push:
branches: [ master ]
```
> **Note**
>
> The branch name may be `main` or `master`. Verify the name of the branch for your repository and update the configuration accordingly.
Now, we need to specify what we actually want to happen within our action (what jobs), we are going to add our build one and select that it runs on the latest Ubuntu instances available:
```yaml
jobs:
build:
runs-on: ubuntu-latest
```
Now, we can add the steps required. The first one checks-out our repository under `$GITHUB_WORKSPACE`, so our workflow can access it. The second is to use our PAT and username to log into Docker Hub. The third is the Builder, the action uses BuildKit under the hood through a simple Buildx action which we will also setup
{% raw %}
```yaml
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, let the workflow run for the first time and then tweak the Dockerfile to make sure the CI is running and pushing the new image changes:
![CI to Docker Hub](../../ci-cd/images/ci-to-hub.png){:width="500px"}
## Optimizing the workflow
Next, lets look at how we can optimize the GitHub Actions workflow through build cache. This has two main advantages:
1. Build cache reduces the build time as it will not have to re-download all of the images, and
2. It also reduces the number of pulls we complete against Docker Hub. We need to make use of GitHub cache to make use of this.
Let us set up a Builder with a build cache. First, we need to set up cache for the builder. In this example, let us add the path and keys to store this under using GitHub cache for this.
{% raw %}
```yaml
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
```
{% endraw %}
And lastly, after adding the builder and build cache snippets to the top of the Actions file, we need to add some extra attributes to the build and push step. This involves:
- Setting up the builder to use the output of the buildx step, and then
- Using the cache we set up earlier for it to store to and to retrieve
{% raw %}
```yaml
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./
file: ./Dockerfile
builder: ${{ steps.buildx.outputs.name }}
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/simplewhale:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
```
{% endraw %}
Now, run the workflow again and verify that it uses the build cache.
## Push tagged versions to Docker Hub
Earlier, we learnt how to set up a GitHub Actions workflow to a Docker project, how to optimize the workflow by setting up a builder with build cache. Lets now look at how we can improve it further. We can do this by adding the ability to have tagged versions behave differently to all commits to master. This means, only specific versions are pushed, instead of every commit updating the latest version on Docker Hub.
You can consider this approach to have your commits go to a local registry to then use in nightly tests. By doing this, you can always test what is latest while reserving your tagged versions for release to Docker Hub.
This involves two steps:
1. Modifying the GitHub workflow to only push commits with specific tags to Docker Hub
2. Setting up a GitHub Actions file to store the latest commit as an image in the GitHub registry
First, let us modify our existing GitHub workflow to only push to Hub if theres a particular tag. For example:
{% raw %}
```yaml
on:
push:
tags:
- "v*.*.*"
```
{% endraw %}
This ensures that the main CI will only trigger if we tag our commits with `Vn.n.n.` Lets test this. For example, run the following command:
```console
$ git tag -a v1.0.2
$ git push origin v1.0.2
```
Now, go to GitHub and check your Actions
![Push tagged version](../../ci-cd/images/push-tagged-version.png){:width="500px"}
Now, lets set up a second GitHub action file to store our latest commit as an image in the GitHub registry. You may want to do this to:
1. Run your nightly tests or recurring tests, or
2. To share work in progress images with colleagues.
Lets clone our previous GitHub action and add back in our previous logic for all pushes. This will mean we have two workflow files, our previous one and our new one we will now work on.
Next, change your Docker Hub login to a GitHub container registry login:
{% raw %}
```yaml
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
```
{% endraw %}
To authenticate against the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry), use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) for the best security and experience.
You may need to [manage write and read access of GitHub Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio) for repositories in the container settings.
You can also use a [personal access token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) with the [appropriate scopes](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry).
Remember to change how the image is tagged. The following example keeps latest as the only tag. However, you can add any logic to this if you prefer:
{% raw %}
```yaml
tags: ghcr.io/${{ github.repository_owner }}/simplewhale:latest
```
{% endraw %}
![Update tagged images](../../ci-cd/images/ghcr-logic.png){:width="500px"}
Now, we will have two different flows: one for our changes to master, and one for our pull requests. Next, we need to modify what we had before to ensure we are pushing our PRs to the GitHub registry rather than to Docker Hub.
{% include gha-tutorial.md %}
## Next steps