Photo by Jungwoo Hong on Unsplash
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 Dockerfilerelease
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 possibleit 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 Herokuinfo
: 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