Builds Docker images for Marea releases. Owns the docker: schema
block (at deploy and release level), contributes the release
type: enum (:elixir, :dockerfile_dir), and extends the build
parent subcommand with docker, show-dockerfile, and
show-docker-versions.
Optional. Enable by listing Marea.Plugins.Docker under plugins:
in marea.yaml. plugin_deps: [Marea.Plugins.Build, …] so listing
Docker also brings in Build (and any plugin that depends on Docker —
notably Marea.Plugins.Helm and docker.python — pulls Docker in
transitively, so most projects don't need to list Docker explicitly).
Schema contributions
deploys.<d>.docker:— deploy-level defaults for the build.deploys.<d>.releases.<r>.docker:— per-release overrides; deep-merged with the deploy-level block.- Adds
:elixirand:dockerfile_dirto thedeploys.<d>.releases.<r>.type:enum viaMarea.Plugins.Base.marea_release_types/1. With only this plugin contributing,:elixiris the default. - A release-level refinement (
validate_release) that enforces the docker keys you set match the chosentype:.
The full set of docker: keys:
| Field | :elixir | :dockerfile_dir | Description |
|---|---|---|---|
template | yes | — | Dockerfile template name. Defaults to the built-in dockerfile_v01. Overrides live in <marea_dir>/templates/. |
elixir_vsn | yes | — | Elixir base image version. Auto-detected from your local install if omitted. |
rebuild_deps | yes | — | List of dep names cleaned and recompiled inside the build (useful for native deps). |
path_deps | yes | — | Local path deps to rsync into the build context (useful for monorepos). |
dockerfile_dir | — | required | Directory under marea_dir containing a Dockerfile. |
args | — | yes | Map of --build-arg KEY=value to pass to docker build. |
platform | yes | yes | Either linux/amd64 or linux/arm64. Translated to --platform. |
Python venvs nest inside docker:. They are contributed by the
docker.python plugin as a docker.python:
sub-block.
Commands
marea build
├── docker --deploy <d> --release <r> --mix-env <env>
│ [--template <name>] [--platform <p>] [--no-cache]
├── show-dockerfile --deploy <d> --release <r>
│ [--template <name>] [--elixir-vsn <v>]
└── show-docker-versions [--elixir-vsn <v>]--deploy and --release default to the most recently used values
(persisted in <state_dir>/last_values).
marea build docker
Builds a Docker image of the release. The flow depends on the
release's type: in marea.yaml:
type: elixir (default)
- Run
mix release(viaMarea.Plugins.Build.build/2). rsyncapps/,deps/,rel/,mix.exs,mix.lock,config/*.exs, and_build/prodinto<state_dir>/build/(so the build context is small and reproducible).- Copy any
path_deps:entries into the build context too. - Render the Dockerfile template (
docker.template, defaultdockerfile_v01) into<state_dir>/build/Dockerfile. docker build -t <release>:<vsn> .from<state_dir>/build/.- Tag the image as
<release>:latest.
--no-cache adds --no-cache to docker build. --platform is
forwarded as --platform <value>.
type: dockerfile_dir
- Run
mix releasefor the chosen release. docker builddirectly from<marea_dir>/<docker.dockerfile_dir>(no rsync, no template).- Pass any entries from
docker.args:as--build-args. - Tag the image as
<release>:<vsn>and<release>:latest.
marea build show-dockerfile
Renders the Dockerfile that would be used for build docker,
without running anything. Useful for reviewing template overrides.
marea build show-docker-versions
Spins up the Elixir Alpine base image for the resolved Elixir version and prints:
| Docker image | Elixir | OTP (full) | ERTS | Alpine |
|---|---|---|---|---|
| elixir:1.17.3-alpine | 1.17.3 | 26.2.1 | 14.2 | 3.20.0 |
Followed by a suggested .tool-versions snippet so your local Elixir
matches what the build is producing.
Public helpers
Other plugins compose docker builds by calling these:
config/1— resolves the per-release Docker config (defaults + deploy-level + release-level + CLI overrides).make_env/2— for:elixirreleases, stages the build context and writes the rendered Dockerfile. No-op for:dockerfile_dir.build_image/2— runsdocker buildand tags the result.expand_template/3— renders a Dockerfile EEx template with the given assigns, looking under<marea_dir>/templates/first and falling back to the built-indocker/templates inMarea.Templates.elixir_vsn/1— detects the local Elixir version (the default fordocker.elixir_vsn).
marea build helm (in Marea.Plugins.Helm) is implemented as a
chain of these helpers: Build.build/2 → Docker.make_env/2 → Docker.build_image/2 → Helm.store_image/1 → docker push → Helm.run_upgrade/1.
Dockerfile templates
marea build docker (and build helm, which calls into it) drives
the image build from a Dockerfile template — an EEx file that Marea
expands into the actual Dockerfile of each build context.
The default template — ~50 MB images
The bundled template
(priv/templates/docker/dockerfile_v01.eex)
is the one most projects will keep using. It is a two- (optionally
three-) stage Alpine build that strips the resulting image down hard:
- Build stage —
elixir:<vsn>-alpinewithmake,git,rsync,g++, etc. Compiles the umbrella, optionally rebuilds the deps listed indocker.rebuild_deps, copiesdocker.path_depsin, and runsmix release $RELEASE_NAME --no-compile. None of this stage ships in the final image. - Plugin-contributed extra stages — emitted via
<%= @extra_stages %>in the template.docker.pythonuses this to inject apython-builderstage when a release setsdocker.python:. - Application stage — a vanilla
alpineimage with just three runtime packages installed (libstdc++,ncurses-libs,openssl), an unprivilegedappuser, the release tarball extracted at/opt/<release>/, andstart.sh/console.sh/consoleentry points. After those steps,<%= @extra_app_steps %>lets plugins splice in extraCOPY/RUNlines (docker.pythonuses this to copy/opt/venvfrom the python-builder stage).
The result is typically 40–60 MB for a moderately-sized Elixir
release (Alpine ≈ 5 MB + Erlang runtime in the release ≈ 25–35 MB +
your beam files + a handful of .sos). For comparison, the same
release built on a Debian-based image often lands above 400 MB. With
the docker/python plugin enabled you add roughly the size of your uv venv
on top.
The default image also embeds the build's git version into /opt/vsn
and exposes a console shell command that runs
<release>/bin/<release> remote, so attaching to a running pod with
kubectl exec -ti <pod> console lands you in an IEx session against
the live node.
Where templates are found
When a release sets docker.template: foo (the default is
dockerfile_v01), Marea looks for the template in this order:
<marea_dir>/templates/foo— your project-local templates, matched without any extension (the file on disk is justfoo, no.eex).- Marea's built-in templates, embedded into the binary at compile
time from
priv/templates/docker/. Marea looks them up under thedocker/prefix and thedockerprefix's default.eexextension is appended automatically, sopriv/templates/docker/dockerfile_v01.eexis referenced asdockerfile_v01.
If neither matches, marea build docker aborts with a clear
"template not found" error. As with Helm templates, you can drop a
file into <marea_dir>/templates/ to either:
- Add a brand-new template name and select it via
--template <name>ordocker.template: <name>inmarea.yaml. - Override
dockerfile_v01for a specific project — same name, different content — and have all releases pick it up automatically.
Starting from the built-in
The fastest way to write a custom Dockerfile is to copy the default and adapt it:
mkdir -p marea.d/templates
# from a checkout of the marea source
cp ../marea/priv/templates/docker/dockerfile_v01.eex marea.d/templates/dockerfile_v01
Note the destination has no extension — that's the key Marea looks up. You can also point at a different name and keep the default available side-by-side:
cp ../marea/priv/templates/docker/dockerfile_v01.eex marea.d/templates/dockerfile_debian
Then in marea.yaml:
deploys:
staging:
releases:
api:
type: elixir
docker:
template: dockerfile_debianmarea build show-dockerfile --deploy staging --release api prints
the rendered Dockerfile without running docker build, so you can
iterate on the template safely.
Template anatomy
A template is plain EEx that produces a Dockerfile. Marea evaluates
it once per build; the output is written to
<state_dir>/build/Dockerfile and docker build . is run from
that directory.
The assigns Marea passes in:
| Assign | Type | Notes |
|---|---|---|
@elixir_vsn | string | Either docker.elixir_vsn from marea.yaml, --elixir-vsn, or your local elixir --version. Drives the FROM elixir:<vsn>-alpine line. |
@rebuild_deps | [String.t()] | Deps to mix deps.clean && deps.compile again inside the build stage (e.g. native NIFs that need rebuilding for Alpine). |
@path_deps | [String.t()] | Local path deps that Marea has already rsync-ed into the build context next to apps/ and deps/. |
@extra_stages | string | Extension point — extra FROM … AS … stages between the build and app stages. Other plugins append fragments here via Marea.Plugins.Docker.marea_dockerfile_assigns/2. |
@extra_app_steps | string | Extension point — extra COPY / RUN lines inside the final app stage. |
Plugins that want to influence the rendered Dockerfile implement
Marea.Plugins.Docker.marea_dockerfile_assigns/2 and append to one
or both extension points. docker.python is the canonical example —
see docker/python.
The Dockerfile also receives standard docker build args at build
time, set by the build plugin:
| Build arg | Set by Marea to |
|---|---|
RELEASE_NAME | The release name from releases:. |
RELEASE_OPTIONS | --overwrite (always). |
MAREA_DEPLOY | deploys.<deploy>.name or the deploy key. |
MAREA_TARGET | release, docker, or helm (the build kind). |
MAREA_GIT_VSN | The git-based release version (date + sha). |
Use them inside the template via standard Dockerfile ARG/ENV —
the default template threads them through to MIX_ENV=prod,
/opt/vsn, etc.
Going fully custom: type: dockerfile_dir
For images that are not Elixir releases at all (sidecars, migration
jobs, auth proxies, NGINX configs, …) skip the EEx template
entirely. Set type: dockerfile_dir on the release and point at a
directory:
releases:
proxy:
type: dockerfile_dir
docker:
dockerfile_dir: proxy # resolves to <marea_dir>/proxy/
args:
UPSTREAM_HOST: apiIn this mode Marea does no template expansion and no rsync — it just
runs docker build from <marea_dir>/<dockerfile_dir>, forwarding
any docker.args as --build-arg key=value. Marea still tags the
image as <release>:<vsn> and <release>:latest, so it slots into
the same build helm push/upgrade pipeline as any Elixir release.
How docker config is resolved
For each call, Marea computes the effective Docker config by deep-merging, in order (later wins):
- Defaults:
template: dockerfile_v01,rebuild_deps: [],path_deps: [],args: %{}. deploys.<deploy>.docker.deploys.<deploy>.releases.<release>.docker.- CLI options (
--template,--platform,--elixir-vsn).
elixir_vsn falls back to your local
elixir -e 'IO.write(System.version())' if not set anywhere.