@halfmatthalfcat/lerna-docker v1.0.6
lerna-docker
Streamline building Docker containers from lerna monorepo projects.
When To Use lerna-docker
lerna-docker solves a bit of a niche problem, albeit a frustrating one. If you:
- Are using
lerna
. - Have multiple deployable projects that depend on common packages.
- Need those deployable projects built into Docker containers.
- Want to only build those deployable projects when there are changes to it or it's dependencies.
...this is the library for you
Usage
There are multiple ways to use lerna-docker
but you'll most likely use it within CI in some fashion.
As An NPM Package
yarn add @halfmatthalfcat/lerna-docker
or
npm i @halfmatthalfcat/lerna-docker
After installing into your project, you can either add a script into your package.json
and call
lerna-docker
from there or you can call ./node_modules/@halfmatthalfcat/lerna-docker/index.js
directly.
As A Github Action
lerna-docker
comes out-of-the-box as a Github Action however, you still must install it into your project as
a dev dependency. This is mostly to avoid committing node_modules into this repo. To use it in a Github Workflow, can do
the following:
jobs:
build:
steps:
# all your usual build steps
- name: Dockerize
uses: ./node_modules/@halfmatthalfcat/lerna-docker
env:
# any/all the configuration options
Configuration
Variable | Required | Description |
---|---|---|
VERSION_PREFIX | X | The SemVer prerelease prefix (rc, dev, pr) |
VERSION_PRERELEASE | The SemVer prerelease to "force" | |
GIT_EMAIL | X | The git email to use when tagging |
GIT_USERNAME | X | The git username to use when tagging |
DOCKER_REGISTRY | X | The docker registry to publish to |
DOCKER_REGISTRY_USERNAME | An optional username to use when auth-ing against a docker registry | |
DOCKER_REGISTRY_PASSWORD | An optional password to use when auth-ing against a docker registry | |
DOCKER_IMAGE_PREFIX | An optional docker image prefix to use in the final image path | |
DOCKERBUILD_ARG* | Optional build args to use when building | |
DOCKERLABEL* | Optional image labels to attach when building |
How lerna-docker Works
There are certain requirements/concessions necessary in order for lerna-docker to work.
1. You are already using lerna
.
1. You technically don't have to be using lerna, however a properly configured lerna.json
file at the root of your project is necessary.
2. You have a top-level /docker
directory that contains Dockerfiles for the projects which require containerizing.
1. The Dockerfiles should directly map to the package name. Packages that do not have an associated Dockerfile will not be built.
3. You are comfortable with a time-based SemVer tag pattern (year
.month
.date
).
High Level Flow
- Changes are made to the repository to any or all dependencies.
- Those changes are pushed to the origin.
lerna changed
is ran to determine which packages changed from the previous tag.- If any packages that have changed have an associated Dockerfile under
/docker
, that Dockerfile is built against the root of the project. - A new SemVer tag is created based on the current date and current build against the trunk.
- The container(s) are pushed to their respective registries.
- The created tag is push to the origin.
- This is a manual step done by the pipeline after successful pushes
Example
Consider the following directory structure:
project_root/
├─ docker/
│ ├─ user_service.Dockerfile/
│ ├─ auth_service.Dockerfile/
├─ packages/
│ ├─ common/
│ ├─ models/
│ │ ├─ dependencies: common
│ ├─ user_service/
│ │ ├─ dependencies: common, models
│ ├─ auth_service/
│ │ ├─ dependencies: common, models
├─ .dockerignore
├─ lerna.json
We have four packages (common, models, user_service and auth_service) that all have various dependencies on each other. We want to deploy user_service and auth_service both independently and if/when their dependencies on other packages change.
Changes to auth_service
Direct changes to auth_service
will trigger a build of auth_service
alone during CI. A new container of auth_service
will be created with the latest tag and pushed to the registry.
Changes to models
Both auth_service
and user_service
have dependencies on models
and thus, will be built, tagged with the latest version
and pushed to the registry.
Changes to common
Since everything is dependent on common
, everything has "changed". However, models
does not have an associated Dockerfile
under /docker
, so no container will be built for models
however, containers will be built, tagged and pushed for
user_service
and auth_service
.
Versioning
lerna-docker
takes an opinionated approach to versioning. The primary reasoning behind this is
traditional major.minor.patch
versioning is difficult in a monorepo. How do we decide what to bump when?
Projects like Conventional Commits
try to accomplish this through commit messages however lerna-docker
strives to streamline this process.
Because of this, it's probably less appropriate to use lerna-docker
for packaging libraries that need to abide by
major.minor.patch
to telegraph actual changes to the project.
The versioning for lerna-docker
operates as so:
{year}. - the current year
{month}. - the current month
{day}. - the current day
-{prefix}. - a user-defined prerelease prefix (rc, dev, pr)
{prerelease} - a prerelease version (automatic or manual)
Imagine it's the first day of the year and we've designated builds against main
to hold the prefix rc
, for
release candidate. The resulting first version of the first commit on main would be 2023.1.1-rc.0
.
Any subsequent builds for that day would increment the rc
version monotonically. The value would reset to 0
on the following day.
Now let's imagine we want to build containers for each commit of a pull request. For this we'll designate the
prefix to be pr
. We can manually define the prerelease version (as an environment variable) instead of letting
lerna-docker
manually increment one. For example, we could use the Pull Request number and Workflow run as our
prerelease, resulting in a tag such as 2023.1.1-pr.22.53
. The prerelease
value should be a valid SemVer prerelease value.