Every time you push code, someone has to build it, test it and ship it. Doing that by hand is slow and easy to get wrong. GitHub Actions lets you automate the whole job inside GitHub itself, with no separate server to maintain. In this tutorial you will build a real pipeline: every push to your main branch will build a Docker image and publish it to Docker Hub automatically.

This is the same flow used by many teams to release containerised applications, and it is a great first CI/CD project for your portfolio. If you are new to containers, read our Docker for Beginners guide first.

How GitHub Actions Works

GitHub Actions has only a few concepts, and once you know them every workflow file becomes easy to read:

What You Need Before Starting

If you do not have an application yet, create a tiny one. This example serves a static page with nginx:

# Dockerfile FROM nginx:alpine COPY index.html /usr/share/nginx/html/index.html EXPOSE 80

Add a simple index.html next to it, commit both files and push them to GitHub.

Step 1: Create a Docker Hub Access Token

Never put your Docker Hub password in a workflow. Create an access token instead, which you can revoke at any time without changing your password.

  1. Sign in to Docker Hub and open Account settings → Personal access tokens (the exact menu name can change, so look under Security if you do not see it).
  2. Generate a new token with a clear name such as github-actions.
  3. Give it read and write permission for repositories, then copy the token. You will not be able to see it again.

Step 2: Add Secrets to Your GitHub Repository

In your GitHub repository, open Settings → Secrets and variables → Actions and create two repository secrets:

Why secrets matter

Anything you commit to a public repository is visible to everyone, including automated scanners that look for leaked passwords. Secrets are stored encrypted and GitHub hides their values in workflow logs.

Step 3: Write the Workflow File

Create the file .github/workflows/docker-publish.yml with the following content:

name: Build and Push Docker Image on: push: branches: [ "main" ] workflow_dispatch: jobs: docker: runs-on: ubuntu-latest steps: - name: Check out the code uses: actions/checkout@v4 - name: Log in to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push uses: docker/build-push-action@v6 with: context: . push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/my-demo-app:latest ${{ secrets.DOCKERHUB_USERNAME }}/my-demo-app:${{ github.sha }}

Here is what each part does:

The action versions shown here were current when we wrote this guide. Check the Marketplace page of each action for the latest major version before you copy the file into a real project.

Step 4: Run It and Watch the Pipeline

Commit the workflow file and push it to main. Then open the Actions tab in your repository. You will see a new run with a yellow dot while it works and a green tick when it succeeds. Click the run to see every step and its logs. When it finishes, open your repository on Docker Hub and you will find the new image with both tags.

Test it on your own machine:

# Replace YOUR_USERNAME with your Docker Hub username docker pull YOUR_USERNAME/my-demo-app:latest docker run -d -p 8080:80 YOUR_USERNAME/my-demo-app:latest # Open http://localhost:8080 in your browser

Add a Test Step Before Publishing

A real pipeline should never publish an image that has not been checked. Add a job that runs your tests and make the Docker job wait for it with needs:

jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests run: | echo "Run your project tests here, for example: npm test or pytest" docker: needs: test # only runs if the test job passes runs-on: ubuntu-latest steps: # ... same steps as before ...

If the test job fails, the Docker job is skipped and nothing is published. This is the core idea of CI/CD: automatic checks first, automatic delivery second.

Common Problems and Fixes

Best Practices

What to Learn Next

Now that you can build and publish an image automatically, the next step is deploying it. You can run the image on a server, on AWS, or on Kubernetes. Read our Kubernetes for Beginners guide to see where your container goes next, and compare tools in Jenkins vs GitHub Actions. You can also follow the step-by-step video versions in our free DevOps tutorials.

Build Real CI/CD Pipelines with Mentors

Our DevOps program covers GitHub Actions, Docker, Kubernetes, Terraform and AWS through live projects.

Apply for the Program →