diff --git a/.dockerignore b/.dockerignore index 81a4ac6..61a7393 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,22 +1,45 @@ -# Ignore version control files +# 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 -.gitignore +!.git/HEAD +!.git/refs + +# Common development/test artifacts +/cover/ +/doc/ +/test/ +/tmp/ +.elixir_ls + +# Mix artifacts +/_build/ +/deps/ +*.ez -# Ignore build artifacts -_build -deps +# Generated on crash by the VM +erl_crash.dump -# Ignore editor files -*.swp -*.swo -*.swn -*.swm -*.swl -*.swk -*.swc -*.swo -*.beam -*.iml -.idea -.vscode -.editorconfig +# Static artifacts - These should be fetched and built inside the Docker image +/assets/node_modules/ +/priv/static/assets/ +/priv/static/cache_manifest.json diff --git a/Dockerfile b/Dockerfile index 56a076a..ee7b412 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,74 @@ +# 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?page=1&name=ubuntu +# https://hub.docker.com/_/ubuntu?tab=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?tab=tags&page=1&name=bullseye-20240904-slim - for the release image +# - https://pkgs.org/ - resource for finding needed packages +# - Ex: hexpm/elixir:1.17.3-erlang-27.0.1-debian-bullseye-20240904-slim +# ARG ELIXIR_VERSION=1.17.3 -ARG OTP_VERSION=27.1 -ARG ALPINE_VERSION=3.19.3 +ARG OTP_VERSION=27.0.1 +ARG DEBIAN_VERSION=bullseye-20240904-slim -ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-alpine-${ALPINE_VERSION}" -ARG RUNNER_IMAGE="alpine:${ALPINE_VERSION}" +ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}" FROM ${BUILDER_IMAGE} as builder -ENV MIX_ENV="prod" +# install build dependencies +RUN apt-get update -y && apt-get install -y build-essential git \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +# prepare build dir WORKDIR /app -# install build tools +# 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 files +# 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 then create binary +# Compile the release RUN mix compile -RUN mix release +# 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} +RUN apt-get update -y && \ + apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \ + && apt-get clean && rm -f /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 @@ -48,4 +84,9 @@ COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/timecopsync_p USER nobody -CMD ["/app/bin/server"] \ No newline at end of file +# 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/lib/timecopsync_projects_api/release.ex b/lib/timecopsync_projects_api/release.ex new file mode 100644 index 0000000..6619352 --- /dev/null +++ b/lib/timecopsync_projects_api/release.ex @@ -0,0 +1,28 @@ +defmodule TimecopsyncProjectsApi.Release do + @moduledoc """ + Used for executing DB release tasks when run in production without Mix + installed. + """ + @app :timecopsync_projects_api + + def migrate do + load_app() + + for repo <- repos() do + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true)) + end + end + + def rollback(repo, version) do + load_app() + {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version)) + end + + defp repos do + Application.fetch_env!(@app, :ecto_repos) + end + + defp load_app do + Application.load(@app) + end +end diff --git a/mix.exs b/mix.exs index d03c34c..026c457 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule TimecopsyncProjectsApi.MixProject do def project do [ app: :timecopsync_projects_api, - version: "1.0.0", + version: "1.0.1", elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, diff --git a/rel/overlays/bin/migrate b/rel/overlays/bin/migrate new file mode 100755 index 0000000..6149576 --- /dev/null +++ b/rel/overlays/bin/migrate @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +exec ./timecopsync_projects_api eval TimecopsyncProjectsApi.Release.migrate diff --git a/rel/overlays/bin/migrate.bat b/rel/overlays/bin/migrate.bat new file mode 100755 index 0000000..ce7c3a0 --- /dev/null +++ b/rel/overlays/bin/migrate.bat @@ -0,0 +1 @@ +call "%~dp0\timecopsync_projects_api" eval TimecopsyncProjectsApi.Release.migrate diff --git a/rel/overlays/bin/server b/rel/overlays/bin/server new file mode 100755 index 0000000..c8a1890 --- /dev/null +++ b/rel/overlays/bin/server @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")" +PHX_SERVER=true exec ./timecopsync_projects_api start diff --git a/rel/overlays/bin/server.bat b/rel/overlays/bin/server.bat new file mode 100755 index 0000000..639d0d9 --- /dev/null +++ b/rel/overlays/bin/server.bat @@ -0,0 +1,2 @@ +set PHX_SERVER=true +call "%~dp0\timecopsync_projects_api" start