Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
106 changes: 106 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# 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-20251208-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: docker.io/hexpm/elixir:1.15.7-erlang-26.2.1-debian-trixie-20251208-slim
#
ARG ELIXIR_VERSION=1.15.7
ARG OTP_VERSION=26.2.1
ARG DEBIAN_VERSION=trixie-20251208-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

RUN mix assets.setup

COPY priv priv

COPY lib lib

# Compile the release
RUN mix compile

COPY assets assets

# compile assets
RUN mix assets.deploy

# 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 curl \
&& 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/ares ./

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", "--"]

EXPOSE 4000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
CMD curl -f http://localhost:4000/ || exit 1

CMD ["sh", "-c", "/app/bin/migrate && /app/bin/server"]
18 changes: 18 additions & 0 deletions assets/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions assets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"tailwindcss": "^4.1.18"
}
}
8 changes: 5 additions & 3 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ config :ex_aws,
s3: [
scheme: "https://",
host: {:system, "ASSET_HOST"},
region: {:system, "AWS_REGION"},
access_key_id: {:system, "AWS_ACCESS_KEY_ID"},
secret_access_key: {:system, "AWS_SECRET_ACCESS_KEY"}
region: {:system, "AWS_S3_REGION"},
access_key_id: {:system, "AWS_S3_ACCESS_KEY_ID"},
secret_access_key: {:system, "AWS_S3_SECRET_ACCESS_KEY"}
]

# Configures Swoosh API Client
Expand All @@ -31,6 +31,8 @@ config :swoosh, api_client: Swoosh.ApiClient.Req
# Disable Swoosh Local Memory Storage
config :swoosh, local: false

config :ares, Ares.Mailer, adapter: Swoosh.Adapters.ExAwsAmazonSES

# Do not print debug messages in production
config :logger, level: :info

Expand Down
14 changes: 9 additions & 5 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ if System.get_env("PHX_SERVER") do
config :ares, AresWeb.Endpoint, server: true
end

config :ares,
from_email_name: System.get_env("FROM_EMAIL_NAME") || "BugsByte",
from_email_address: System.get_env("FROM_EMAIL_ADDRESS") || "no-reply@bugsbyte.org"

if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
Expand Down Expand Up @@ -102,18 +106,18 @@ if config_env() == :prod do
# ## Configuring the mailer
#
# In production you need to configure the mailer to use a different adapter.
# Here is an example configuration for Mailgun:
# Also, you may need to configure the Swoosh API client of your choice if you
# are not using SMTP. Here is an example of the configuration:
#
# config :ares, Ares.Mailer,
# adapter: Swoosh.Adapters.Mailgun,
# api_key: System.get_env("MAILGUN_API_KEY"),
# domain: System.get_env("MAILGUN_DOMAIN")
#
# Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney,
# and Finch out-of-the-box. This configuration is typically done at
# compile-time in your config/prod.exs:
# For this example you need include a HTTP client required by Swoosh API client.
# Swoosh supports Hackney and Finch out of the box:
#
# config :swoosh, :api_client, Swoosh.ApiClient.Req
# config :swoosh, :api_client, Swoosh.ApiClient.Hackney
#
# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
end
101 changes: 47 additions & 54 deletions lib/ares/accounts/user_notifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,39 @@ defmodule Ares.Accounts.UserNotifier do
alias Ares.Accounts.User
alias Ares.Mailer

# Delivers the email using the application mailer.
defp deliver(recipient, subject, body) do
email =
new()
|> to(recipient)
|> from({"Ares", "contact@example.com"})
|> subject(subject)
|> text_body(body)

with {:ok, _metadata} <- Mailer.deliver(email) do
{:ok, email}
end
use Phoenix.Swoosh, view: AresWeb.EmailView

defp base_html_email(recipient, subject) do
sender = {Mailer.get_sender_name(), Mailer.get_sender_address()}

phx_host =
if System.get_env("PHX_HOST") != nil do
"https://" <> System.get_env("PHX_HOST")
else
""
end

new()
|> to(recipient)
|> from(sender)
|> subject("[#{elem(sender, 0)}] #{subject}")
|> assign(:phx_host, phx_host)
end

@doc """
Deliver instructions to update a user email.
"""
def deliver_update_email_instructions(user, url) do
deliver(user.email, "Update email instructions", """

==============================

Hi #{user.email},

You can change your email by visiting the URL below:

#{url}

If you didn't request this change, please ignore this.

==============================
""")
email =
base_html_email(user.email, "Update your email address")
|> assign(:user_name, user.name)
|> assign(:confirm_email_link, url)
|> render_body("confirm_email.html")

case Mailer.deliver(email) do
{:ok, _metadata} -> {:ok, email}
{:error, reason} -> {:error, reason}
end
end

@doc """
Expand All @@ -52,36 +53,28 @@ defmodule Ares.Accounts.UserNotifier do
end

defp deliver_magic_link_instructions(user, url) do
deliver(user.email, "Log in instructions", """

==============================

Hi #{user.email},

You can log into your account by visiting the URL below:

#{url}

If you didn't request this email, please ignore this.

==============================
""")
email =
base_html_email(user.email, "Log in to your account")
|> assign(:user_name, user.name)
|> assign(:magic_link, url)
|> render_body("magic_link.html")

case Mailer.deliver(email) do
{:ok, _metadata} -> {:ok, email}
{:error, reason} -> {:error, reason}
end
end

defp deliver_confirmation_instructions(user, url) do
deliver(user.email, "Confirmation instructions", """

==============================

Hi #{user.email},

You can confirm your account by visiting the URL below:

#{url}

If you didn't create an account with us, please ignore this.

==============================
""")
email =
base_html_email(user.email, "Confirm your email")
|> assign(:user_name, user.name)
|> assign(:confirm_email_link, url)
|> render_body("confirm_email.html")

case Mailer.deliver(email) do
{:ok, _metadata} -> {:ok, email}
{:error, reason} -> {:error, reason}
end
end
end
8 changes: 8 additions & 0 deletions lib/ares/mailer.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
defmodule Ares.Mailer do
use Swoosh.Mailer, otp_app: :ares

def get_sender_name do
Application.get_env(:ares, :from_email_name)
end

def get_sender_address do
Application.get_env(:ares, :from_email_address)
end
end
Loading