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 :elixir and :dockerfile_dir to the deploys.<d>.releases.<r>.type: enum via Marea.Plugins.Base.marea_release_types/1. With only this plugin contributing, :elixir is the default.
  • A release-level refinement (validate_release) that enforces the docker keys you set match the chosen type:.

The full set of docker: keys:

Field:elixir:dockerfile_dirDescription
templateyesDockerfile template name. Defaults to the built-in dockerfile_v01. Overrides live in <marea_dir>/templates/.
elixir_vsnyesElixir base image version. Auto-detected from your local install if omitted.
rebuild_depsyesList of dep names cleaned and recompiled inside the build (useful for native deps).
path_depsyesLocal path deps to rsync into the build context (useful for monorepos).
dockerfile_dirrequiredDirectory under marea_dir containing a Dockerfile.
argsyesMap of --build-arg KEY=value to pass to docker build.
platformyesyesEither 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)

  1. Run mix release (via Marea.Plugins.Build.build/2).
  2. rsync apps/, deps/, rel/, mix.exs, mix.lock, config/*.exs, and _build/prod into <state_dir>/build/ (so the build context is small and reproducible).
  3. Copy any path_deps: entries into the build context too.
  4. Render the Dockerfile template (docker.template, default dockerfile_v01) into <state_dir>/build/Dockerfile.
  5. docker build -t <release>:<vsn> . from <state_dir>/build/.
  6. Tag the image as <release>:latest.

--no-cache adds --no-cache to docker build. --platform is forwarded as --platform <value>.

type: dockerfile_dir

  1. Run mix release for the chosen release.
  2. docker build directly from <marea_dir>/<docker.dockerfile_dir> (no rsync, no template).
  3. Pass any entries from docker.args: as --build-args.
  4. 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 imageElixirOTP (full)ERTSAlpine
elixir:1.17.3-alpine1.17.326.2.114.23.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 :elixir releases, stages the build context and writes the rendered Dockerfile. No-op for :dockerfile_dir.
  • build_image/2 — runs docker build and 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-in docker/ templates in Marea.Templates.
  • elixir_vsn/1 — detects the local Elixir version (the default for docker.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:

  1. Build stageelixir:<vsn>-alpine with make, git, rsync, g++, etc. Compiles the umbrella, optionally rebuilds the deps listed in docker.rebuild_deps, copies docker.path_deps in, and runs mix release $RELEASE_NAME --no-compile. None of this stage ships in the final image.
  2. Plugin-contributed extra stages — emitted via <%= @extra_stages %> in the template. docker.python uses this to inject a python-builder stage when a release sets docker.python:.
  3. Application stage — a vanilla alpine image with just three runtime packages installed (libstdc++, ncurses-libs, openssl), an unprivileged app user, the release tarball extracted at /opt/<release>/, and start.sh / console.sh / console entry points. After those steps, <%= @extra_app_steps %> lets plugins splice in extra COPY / RUN lines (docker.python uses this to copy /opt/venv from 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:

  1. <marea_dir>/templates/foo — your project-local templates, matched without any extension (the file on disk is just foo, no .eex).
  2. Marea's built-in templates, embedded into the binary at compile time from priv/templates/docker/. Marea looks them up under the docker/ prefix and the docker prefix's default .eex extension is appended automatically, so priv/templates/docker/dockerfile_v01.eex is referenced as dockerfile_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> or docker.template: <name> in marea.yaml.
  • Override dockerfile_v01 for 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_debian

marea 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:

AssignTypeNotes
@elixir_vsnstringEither 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_stagesstringExtension 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_stepsstringExtension 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 argSet by Marea to
RELEASE_NAMEThe release name from releases:.
RELEASE_OPTIONS--overwrite (always).
MAREA_DEPLOYdeploys.<deploy>.name or the deploy key.
MAREA_TARGETrelease, docker, or helm (the build kind).
MAREA_GIT_VSNThe 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: api

In 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):

  1. Defaults: template: dockerfile_v01, rebuild_deps: [], path_deps: [], args: %{}.
  2. deploys.<deploy>.docker.
  3. deploys.<deploy>.releases.<release>.docker.
  4. CLI options (--template, --platform, --elixir-vsn).

elixir_vsn falls back to your local elixir -e 'IO.write(System.version())' if not set anywhere.

Source