Continuous Deployment With Heroku, Docker, And Github Actions

Today we will talk about how to setup a very simple continuous deployment pipeline for a REST API using heroku, docker and github actions

Prerequisites

You need a heroku account to deploy the project. We will use the heroku cli and docker to setup and deploy our project from our command line. Make sure that you have created an account on heroku and install heroku cli and docker. We will need a github account to setup a remote repository and use github actions.

Project Setup

We will create a simple rest api with node and express for this tutorial

  1. create a new directory with name cd-heroku-docker
  2. cd into that directory
  3. run the $ npm init -y command. this will setup a node project in your directory
  4. run the $ npm install express morgan cors command to install our dependencies
  5. run the $ git init command to initialize a git repository

Create a file named index.js and paste the following code into to file. This will be our server code.

index.js
const express = require("express");
const cors = require("cors");
const morgan = require("morgan");

const app = express();

app.use(cors());
app.use(morgan("common"));

app.get("/ping", (req, res) => {
  res.json({ message: "pong" });
});

app.listen(process.env.PORT || 8000, () => {
  console.log(`server started on port ${process.env.PORT || 8000}`);
});

When it will respond with {"message":"pong"} when it receives a get request on /ping route.

Add a start script to your package.json file as shown below

package.json
...
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
...

We start the server at the port process.env.PORT because heroku will use that while deploying our container. Test the script by running $ npm start in your command line and going to http://localhost:8000/ping

Create a file named .gitignore and paste the following code into to file. It will prevent node_modules from being committed to github.

.gitignore
node_modules

Create a file named Dockerfile without any file extension and paste the following code into it. Docker and heroku will use this to build our docker image

Dockerfile
FROM node:lts-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

CMD ["npm","start"]

Notice that the Dockerfile does not contain the EXPOSE option because heroku does not support it.

Create a file named .dockerignore and paste the following code into it

.dockerignore
node_modules

This will prevent docker from copying our node_modules directory.

At this point our file structure should look something like this

|-- node_modules/
|-- index.js
|-- Dockerfile
|-- .dockerignore
|-- .gitignore
|-- package.json
|-- package.lock.json

Docker Test

Lets commit our code to our local git repository and test our project locally by building out docker image and spinning up a container. After that we will setup a heroku project and github repository with github actions to enable continuous deployment. Run the following commands

  • $ git add .
  • $ git commit -m 'project setup'
  • $ docker build -t cd-heroku-docker .
  • $ docker run -p 8000:8000 cd-heroku-docker
  • visit http://localhost:8000/ping your will see {"message":"pong"} in the browser
  • you will also see the logs of the application in the terminal where you ran the docker run command you can stop the container by pressing CTRL + C

Now we have created a docker image and successfully tested it by running a container. Now we will deploy this project to heroku and then setup a remote github repository for github actions

Deploying With Heroku

Make sure you have the heroku cli installed by running $ heroku --version command. Run the following commands to authenticate the heroku cli

  • $ heroku login - this command will open a browser tab. Log in with you heroku account to continue
  • $ heroku container login - to log into the container registry
  • $ heroku create <your project name here>

this command will create a new heroku app in your account you can name your application anything you want. You might get an error while running this command if your project name is already taken by someone else. Try again with a different project name. After running this command heroku will give you a link which looks like https://<your project name>.herokuapp.com this is the url where our app will be hosted. If you visit this link you will see the default heroku app page because we have not yet deployed our application yet. Let's deploy our application.

run the following command

  • $ heroku container:push web -a <your app name>

This will build your image and push it to the heroku container registry. Now let's deploy our image that we just pushed. Run the following command

  • $ heroku container:release web -a <your app name>

Now if you visit https://<your app name>.herokuapp.com/ping you will get{'message':'ping'} back. We have successfully deployed our application to heroku

Setup GitHub action

We have successfully deployed our app on heroku. But we will have to redeploy our application every time we do some changes. What if our code deployed itself automatically every time we made some changes. That's why we are going to setup a GitHub action to do just that

To do that we will use this github action. Someone has already put in the work to create a github action for heroku, we are just going to use it. But before that we are going to need a heroku api key so that github can push to heroku on our behalf. To create a api key run $ heroku authorizations:create command. This command will create an token and print it on the screen. Copy and paste it somewhere safe and secure we will need this token in a moment.

The next thing to do is to create a github repository for your project. I am not going to go in depth about this, I hope that you can create a repository on github once you have done that copy the repository link that looks something like https://github.com/<user name>/<repository name>.git let us add it to our application.

Run the following command to add a remote repository and push our code to the remote repository

  • $ git remote add origin <link of your repository>
  • $ git push -u origin master

Now we have pushed our code to the remote repository lets add a github action to automate deployment. In our project create a folder named .github and inside it create another folder named workflows and inside it create a file named main.yml

.github/workflows/main.yml
name: Deploy

on:
  push:
    branches:
      - master

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3.12.12 # This is the action
        with:
          heroku_api_key: ${{secrets.HEROKU_API_KEY}}
          heroku_app_name: "<your app name>" #Must be unique in Heroku
          heroku_email: "<your heroku email>"
          usedocker: true

replace <your app name> and <your heroku email> with the correct values.

Now we need to add our heroku api key that we created and copied a few minutes ago. Visit your github repository in your browser and then navigate to settings --> secrets --> new repository secret. Then in the key field add HEROKU_API_KEY and in value field paste the token you copied a few minutes ago and click on add secret

Coming back to our terminal. Run the following commands to push our changed code to our repository to see the magic

  • $ git add .
  • $ git commit -m 'add github action'
  • $ git push

If you visit your repository's actions tab in your browser you will see that the github action has started running and it will build and deploy our new image soon.

Now every time you make some changes in your master branch and push it to github this github action will make use of heroku to build and deploy the new docker image. Test it by changing the message you are sending from your application and pushing it to the master branch and then visiting your heroku application in the browser

Thank you for reading the blog post I hope that you found it helpful