Skip to content
How to Publish Docker Images to Docker Hub with GitHub Actions

How to Publish Docker Images to Docker Hub with GitHub Actions

Published: at 08:56 AM

Docker has been gaining popularity ever since because it provides a simple way to package your code and ship it as a runnable image.

Docker also brings you Docker Hub, where storing your images is free.

Combined with GitHub Actions, you automate the process of building and pushing images to Docker Hub.

This is one of the most cost-effective ways to deploy MVPs and most of your web apps. This is how I'm doing it, so I wanted to share my process here.

I assume you have a Node.js application you want to Dockerize and deploy, and you have Docker installed on the machine or where you want to deploy the image. If not, see Install Docker Engine (I also use Docker Desktop locally).

Terminology

Create the Dockerfile

Open your Node.js project, and in the root directory, create a file Dockerfile.

This file describes how your application should be packaged and run. Some of these commands are self-explanatory, but I added comments to make things clear:

# Use a base image with the latest Node.js LTS installed
FROM node:20

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json to the working directory
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code to the working directory
COPY . .

# Build the app
RUN npm run build

# Start the app
CMD ["npm", "start"]

Building and running your image locally

Before we dive into how to automatize this, let's make sure that this actually works.

Build

To create a Docker image using the Dockerfile you just made, run the following command in your command line:

docker build -t my-image:latest .

The format is always [name of the image]:[tag]. This will help you better organize your images and align with how other docker images are named.

After the command runs, type docker images to list the images on your machine. I have a bunch of them, but you can see my-image that I've just built:

Run

Running the image will start a container based on the image. You can run docker ps to list the currently running containers.

To run the image you just created, type this into your command line:

docker run my-tag

Imagine docker containers like machines on your local network. They have their network inside the container that, by default, you can't access. If you spin up a Node.js app inside the container, you can't access it unless you expose the container's specific port to your machine. So, for example, if your Node.js app is running on port 3000, you might want to run the above command with the -p argument that does this:

docker run -p 3000:3000 my-image

This command starts a new Docker container just as before, and the -p option maps port 3000 of the container to port 3000 of the host.

If you want to supply environment variables from an .env file to your application, use the --env-file flag.

To run the image in background mode, add the -d flag:

docker run -d -p 3000:3000 --env-file .env my-image

To list the running docker containers after this command type docker ps:

The running container shows up in docker ps.

I ran the image using the above command, and it showed up in the running containers after I typed docker ps again.

Create a DockerHub account

To easily deploy our images on different locations, for example, on an AWS EC2 instance or a DigitalOcean droplet, you need a way to distribute them.

This is where Docker registries come into play. Docker Hub is a public registry that anyone can use, and Docker searches for images on Docker Hub by default. You can also run your private registry.

You can create a Docker Hub account here if you haven't already.

Create a GitHub Actions workflow

Now that you have ensured our image works, it's time to automate building and uploading it to Docker Hub using GitHub Actions.

If you're new to GitHub Actions, check out Understanding GitHub Actions.

Start by creating a .github/workflows/main.yml file in your project's directory with the following contents:

name: Deploy

on:
  push:
    branches:
      - main # Set a branch to deploy when pushed

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Install
        run: npm install

      - name: Run Typecheck
        run: npm run typecheck

Here's what's happening above:

Push the image to Docker Hub

After the checks have passed, let's publish our image to Docker Hub. Extend your workflow with two more steps:

name: Deploy

on:
  # No changes

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # No changes

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: akoskm/my-image:latest

First, you log in to your Docker Hub account using your username and the token you created as described in Create and manage access tokens. Notice that I added akoskm as a prefix to my image name and tag. You'll have to use your Dockerhub username here.

Then, build and push the docker image to the Docker Hub repository.

After this is complete, you'll find the new image in your DockerHub Repository:

Pull and run the image from DockerHub

Now that you pushed your image to DockerHub, you can pull it on any machine with Docker installed. As mentioned earlier, Docker searches for images on Docker Hub by default.

So, for example, after logging in to your AWS EC2, you can run the following command:

docker pull akoskm/my-image:latest

Finally, run your image as before:

docker run -d -p 3000:3000 --env-file .env akoskm/my-image:latest

I hope you found this article helpful and that it will make your next app launch easier!

If you enjoyed the article, please consider liking it and, more importantly, sharing it with those who might find it useful!

Want to see this in action? Check out my SaaS boilerplate app that I'm building with Remix!

For questions, suggestions, or help, reach out to me on X @akoskm or in the comments.

Thanks,

Akos