From b06cf0b98a95b3819a32a0a2bcfee1a821e423e1 Mon Sep 17 00:00:00 2001 From: Pierre de Lacroix Date: Fri, 23 Jan 2026 00:08:41 +0100 Subject: [PATCH] add docker release --- .dockerignore | 46 ++++++++++++++++++ Dockerfile | 94 +++++++++++++++++++++++++++++++++++++ config/config.exs | 2 - config/dev.exs | 2 + config/runtime.exs | 2 + rel/overlays/bin/server | 5 ++ rel/overlays/bin/server.bat | 2 + 7 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 rel/overlays/bin/server create mode 100755 rel/overlays/bin/server.bat diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5d2adff --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# This file excludes paths from the Docker build context. +# +# By default, Docker's build context includes all files (and folders) in the +# current directory. Even if a file isn't copied into the container it is still sent to +# the Docker daemon. +# +# There are multiple reasons to exclude files from the build context: +# +# 1. Prevent nested folders from being copied into the container (ex: exclude +# /assets/node_modules when copying /assets) +# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc) +# 3. Avoid sending files containing sensitive information +# +# More information on using .dockerignore is available here: +# https://docs.docker.com/engine/reference/builder/#dockerignore-file + +.dockerignore + +# Ignore git, but keep git HEAD and refs to access current commit hash if needed: +# +# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat +# d0b8727759e1e0e7aa3d41707d12376e373d5ecc +.git +!.git/HEAD +!.git/refs + +# Common development/test artifacts +/cover/ +/doc/ +/test/ +/tmp/ +.elixir_ls + +# Mix artifacts +/_build/ +/deps/ +*.ez + +# Generated on crash by the VM +erl_crash.dump + +# Static artifacts - These should be fetched and built inside the Docker image +# https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Release.html#module-docker +/assets/node_modules/ +/priv/static/assets/ +/priv/static/cache_manifest.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7fc188d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,94 @@ +# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian +# instead of Alpine to avoid DNS resolution issues in production. +# +# https://hub.docker.com/r/hexpm/elixir/tags?name=ubuntu +# https://hub.docker.com/_/ubuntu/tags +# +# This file is based on these images: +# +# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image +# - https://hub.docker.com/_/debian/tags?name=trixie-20260112-slim - for the release image +# - https://pkgs.org/ - resource for finding needed packages +# - Ex: docker.io/hexpm/elixir:1.16.2-erlang-26.1.1-debian-trixie-20260112-slim +# +ARG ELIXIR_VERSION=1.16.2 +ARG OTP_VERSION=26.1.1 +ARG DEBIAN_VERSION=trixie-20260112-slim + +ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}" + +FROM ${BUILDER_IMAGE} AS builder + +# install build dependencies +RUN apt-get update \ + && apt-get install -y --no-install-recommends build-essential git \ + && rm -rf /var/lib/apt/lists/* + +# prepare build dir +WORKDIR /app + +# install hex + rebar +RUN mix local.hex --force \ + && mix local.rebar --force + +# set build ENV +ENV MIX_ENV="prod" + +# install mix dependencies +COPY mix.exs mix.lock ./ +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# copy compile-time config files before we compile dependencies +# to ensure any relevant config change will trigger the dependencies +# to be re-compiled. +COPY config/config.exs config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +COPY priv priv + +COPY lib lib + +# Compile the release +RUN mix compile + +# Changes to config/runtime.exs don't require recompiling the code +COPY config/runtime.exs config/ + +COPY rel rel +RUN mix release + +# start a new build stage so that the final image will only contain +# the compiled release and other runtime necessities +FROM ${RUNNER_IMAGE} AS final + +RUN apt-get update \ + && apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \ + && locale-gen + +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en +ENV LC_ALL=en_US.UTF-8 + +WORKDIR "/app" +RUN chown nobody /app + +# set runner ENV +ENV MIX_ENV="prod" + +# Only copy the final release from the build stage +COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/camp_api ./ + +USER nobody + +# If using an environment that doesn't automatically reap zombie processes, it is +# advised to add an init process such as tini via `apt-get install` +# above and adding an entrypoint. See https://github.com/krallin/tini for details +# ENTRYPOINT ["/tini", "--"] + +CMD ["/app/bin/server"] diff --git a/config/config.exs b/config/config.exs index eef8600..80547d3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -29,8 +29,6 @@ config :logger, :default_formatter, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason -config :camp_api, :grist, api_key: System.get_env("GRIST_API_KEY") - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 2858b39..ec2877d 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -42,6 +42,8 @@ config :camp_api, CampApiWeb.Endpoint, # Enable dev routes for dashboard and mailbox config :camp_api, dev_routes: true +config :camp_api, :grist, api_key: System.get_env("GRIST_API_KEY") + # Do not include metadata nor timestamps in development logs config :logger, :default_formatter, format: "[$level] $message\n" diff --git a/config/runtime.exs b/config/runtime.exs index 620db22..1119b30 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -23,6 +23,8 @@ end config :camp_api, CampApiWeb.Endpoint, http: [port: String.to_integer(System.get_env("PORT", "4000"))] +config :camp_api, :grist, api_key: System.get_env("GRIST_API_KEY") + if config_env() == :prod do # The secret key base is used to sign/encrypt cookies and other secrets. # A default value is used in config/dev.exs and config/test.exs but you diff --git a/rel/overlays/bin/server b/rel/overlays/bin/server new file mode 100755 index 0000000..3f23efe --- /dev/null +++ b/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./camp_api start diff --git a/rel/overlays/bin/server.bat b/rel/overlays/bin/server.bat new file mode 100755 index 0000000..f61bf70 --- /dev/null +++ b/rel/overlays/bin/server.bat @@ -0,0 +1,2 @@ +set PHX_SERVER=true +call "%~dp0\camp_api" start