Multiple Docker images on Heroku

Comparing heroku.yml and the Heroku Deploy Maven plugin

In this article, I am presenting the options to deploy multiple Docker images on Heroku. Following the microservices philosophy developers like splitting responsibilities into standalone services which are more easily developed, containerized and deployed.

Although Heroku support for Docker is awesome the platform is not living up to its standards when dealing with multiple containers (á la Docker Compose to be clear).

The post blog will discuss and compare two options:

  • using heroku.yml

  • using the Heroku Docker Maven plugin

Using heroku.yml

Given the popularity (and convenience) of Docker Compose Heroku was introduced heroku.yml back in 2018 with the goal of simulating the same workflow. Did it work? Let’s have a look.

heroku.yml

The goal ofheroku.yml is to encapsulate the Docker configuration of your Heroku application, the different images involved and the stages of the build.

setup:
  addons:
    - plan: heroku-postgresql
      as: DATABASE
  config:
    MY_VAR: my value
    MY_VAR_2: another value
build:
  docker:
    web: web/Dockerfile
    worker: worker1/Dockerfile
    worker: worker2/Dockerfile
# release:
  # copy resources
  # skip in this example
# run:
  # startup script
  # skip in this example

There are 4 phases managed by heroku.yml:

  • setup to provision add-ons (i.e. database, cache) and set the config vars (environment variables)

  • build to create the Docker images (in the example above one web and 2 workers) by specifying the process type and path of each Dockerfile

  • release to run tasks required during the application release (for example copy assets or upgrade database structure)

  • run to define commands to execute at application startup (note: this can be skipped and instead rely on the DockerfileCMD instruction)

Once the heroku.yml is ready the process is straightforward: create the application, set the container stack (Heroku must know you want to use Docker) and push the file.

# create the Heroku app
$ heroku create myapp
$ heroku stack:set container
# commit (in the repository's root)
$ git add heroku.yml
$ git commit -m "Added heroku.yml"
# push to heroku 
git push heroku main

Pros and Cons

Using heroku.yml is overall pretty convenient: the whole application (from pre-requisites to deployment scripts) is defined in a single file and you can build multiple Docker images at once. Good stuff.

However, there are some limitations:

  • it is not possible to create multiple images and deploy them on different applications

  • it requires git to trigger the deployment pipeline (by pushing the changes to the Heroku git repository)

  • only a single web Dyno is possible

  • it addresses the build and deployment needs, but it cannot un-deploy nor check the application(s) information.

Given the above, I have created a Maven plugin to provide the extra features.

Heroku Docker Maven plugin

The idea behind the Heroku Docker Maven plugin is to manage multiple Dockerized services directly from the IDE (or command line) using Maven.

It delivers its best (IMHO) in a Maven multi-module project where each module has its own Dockerfile (maybe not suitable for production but I reckon convenient for development and testing purposes).

Goals

The plugin supports currently 4 goals:

  • deploy: build, push and release the Docker image(s) to Heroku

  • info: print some information about the Heroku application(s) defined in the Maven POM file(s)

  • start: start (scale up) the Heroku application(s)

  • stop: stop (scale down) the Heroku application(s)

Prerequisites

The Heroku CLI must be available in the environment/hosting running the Maven plugin and, obviously, an account to log into the Docker registry.

Configuration

Configure the plugin in the parent POM

<build>
  <plugins>
    <plugin>
      <groupId>com.tweesky.cloudtools</groupId>
      <artifactId>heroku-docker-maven-plugin</artifactId>
      <version>0.0.1</version>
    </plugin>
  </plugins>
</build>

Configure each service (sub-module) accordingly

<build>
  <plugins>
    <plugin>
      <groupId>com.tweesky.cloudtools</groupId>
      <artifactId>heroku-docker-maven-plugin</artifactId>
      <configuration>
        <appName>serviceOne</appName>
        <processType>web</processType>
        <configVars>
          <VAR_ONE>SomeValue</VAR_ONE>
          <VAR_TWO>SomeOtherValue</VAR_TWO>
        </configVars>
      </configuration>
    </plugin>
  </plugins>
</build>

Note above how you can define the application name (appName ), the type of process (processType )and any config vars (configVars ) you might want to provision.

Check out the MavenMultiModule example on GitHub.

Execute

Invoke the plugin with the desired goal, for example deploy to build and push all services

mvn heroku-docker:deploy

Check the status of the applications

mvn heroku-docker:info

It is time to get a coffee ☕ Stop all services to save on your Heroku free quota ;-)

mvn heroku-docker:stop

CI/CD

The plugin can also be used with a CI/CD tool, for example, to rebuild and deploy the Docker images after a code push.

On Github Actions this turns out to be pretty simple using Maven (passing the Heroku API Key for authentication purposes).

- name: Heroku Container Registry login
  env:
     HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
  run: heroku container:login
- name: Build with Maven      
  env:
     HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}  
  run: 
    mvn -B heroku-docker:deploy

See the full workflow file with all steps.

Pros and Cons

While the plugin addresses the heroku.yml limitations (deploy multiple web services, manage multiple applications, no need to configure and use Heroku git) it does require creating (manually) the Heroku application (including the provision of any addon) beforehand.

Contributing

The initial version of the Heroku Docker Maven plugin suits my needs (hopefully yours too) however, I am happy to hear feedback to evolve and improve it further.

One very interesting feature to be considered could be the support ofdocker-compose.yml allowing developers to re-use existing docker-compose files without copying the configuration elsewhere. Interested in contributing? Head to the GitHub repository and/or get in touch!


Resources

GitHub repository with an example using the Heroku Docker Maven plugin

Github repository showing how to configure and use heroku.yml