Skip to content

Commit b7cf37f

Browse files
authored
ops: staging environment (#448)
1 parent 611182b commit b7cf37f

File tree

39 files changed

+693
-60
lines changed

39 files changed

+693
-60
lines changed

.dockerignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This file excludes paths from the Docker build context.
2+
#
3+
# By default, Docker's build context includes all files (and folders) in the
4+
# current directory. Even if a file isn't copied into the container it is still sent to
5+
# the Docker daemon.
6+
#
7+
# There are multiple reasons to exclude files from the build context:
8+
#
9+
# 1. Prevent nested folders from being copied into the container (ex: exclude
10+
# /assets/node_modules when copying /assets)
11+
# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)
12+
# 3. Avoid sending files containing sensitive information
13+
#
14+
# More information on using .dockerignore is available here:
15+
# https://docs.docker.com/engine/reference/builder/#dockerignore-file
16+
17+
.dockerignore
18+
19+
# Ignore git, but keep git HEAD and refs to access current commit hash if needed:
20+
#
21+
# $ cat .git/HEAD | awk '{print ".git/"$2}' | xargs cat
22+
# d0b8727759e1e0e7aa3d41707d12376e373d5ecc
23+
.git
24+
!.git/HEAD
25+
!.git/refs
26+
27+
# Common development/test artifacts
28+
/cover/
29+
/doc/
30+
/test/
31+
/tmp/
32+
.elixir_ls
33+
34+
# Mix artifacts
35+
/_build/
36+
/deps/
37+
*.ez
38+
39+
# Generated on crash by the VM
40+
erl_crash.dump
41+
42+
# Static artifacts - These should be fetched and built inside the Docker image
43+
/assets/node_modules/
44+
/priv/static/assets/
45+
/priv/static/cache_manifest.json

.github/workflows/fly-deploy.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
2+
3+
name: Fly Deploy
4+
on:
5+
push:
6+
branches:
7+
- main
8+
jobs:
9+
deploy:
10+
name: Deploy app
11+
runs-on: ubuntu-latest
12+
concurrency: deploy-group # optional: ensure only one action runs at a time
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: superfly/flyctl-actions/setup-flyctl@master
16+
- run: flyctl deploy --remote-only
17+
env:
18+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,4 @@ npm-debug.log
3939

4040
.env.*
4141
.env
42+

Dockerfile

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
2+
# instead of Alpine to avoid DNS resolution issues in production.
3+
#
4+
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
5+
# https://hub.docker.com/_/ubuntu?tab=tags
6+
#
7+
# This file is based on these images:
8+
#
9+
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
10+
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20240701-slim - for the release image
11+
# - https://pkgs.org/ - resource for finding needed packages
12+
# - Ex: hexpm/elixir:1.17.2-erlang-27.0-debian-bullseye-20240701-slim
13+
#
14+
ARG ELIXIR_VERSION=1.17.2
15+
ARG OTP_VERSION=27.0
16+
ARG DEBIAN_VERSION=bullseye-20240701-slim
17+
18+
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
19+
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
20+
21+
FROM ${BUILDER_IMAGE} as builder
22+
23+
# install build dependencies
24+
RUN apt-get update -y && apt-get install -y build-essential git \
25+
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
26+
27+
# prepare build dir
28+
WORKDIR /app
29+
30+
# install hex + rebar
31+
RUN mix local.hex --force && \
32+
mix local.rebar --force
33+
34+
# set build ENV
35+
ENV MIX_ENV="prod"
36+
37+
# install mix dependencies
38+
COPY mix.exs mix.lock ./
39+
RUN mix deps.get --only $MIX_ENV
40+
RUN mkdir config
41+
42+
# copy compile-time config files before we compile dependencies
43+
# to ensure any relevant config change will trigger the dependencies
44+
# to be re-compiled.
45+
COPY config/config.exs config/${MIX_ENV}.exs config/
46+
RUN mix deps.compile
47+
48+
COPY priv priv
49+
50+
COPY lib lib
51+
52+
COPY assets assets
53+
54+
# compile assets
55+
RUN mix assets.deploy
56+
57+
# Compile the release
58+
RUN mix compile
59+
60+
# Changes to config/runtime.exs don't require recompiling the code
61+
COPY config/runtime.exs config/
62+
63+
COPY rel rel
64+
RUN mix release
65+
66+
# start a new build stage so that the final image will only contain
67+
# the compiled release and other runtime necessities
68+
FROM ${RUNNER_IMAGE}
69+
70+
RUN apt-get update -y && \
71+
apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
72+
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
73+
74+
# Set the locale
75+
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
76+
77+
ENV LANG en_US.UTF-8
78+
ENV LANGUAGE en_US:en
79+
ENV LC_ALL en_US.UTF-8
80+
81+
WORKDIR "/app"
82+
RUN chown nobody /app
83+
84+
# set runner ENV
85+
ENV MIX_ENV="prod"
86+
87+
# Only copy the final release from the build stage
88+
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/safira ./
89+
90+
USER nobody
91+
92+
# If using an environment that doesn't automatically reap zombie processes, it is
93+
# advised to add an init process such as tini via `apt-get install`
94+
# above and adding an entrypoint. See https://github.com/krallin/tini for details
95+
# ENTRYPOINT ["/tini", "--"]
96+
97+
CMD ["/app/bin/server"]

bin/deploy

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
3+
set -Eeuo pipefail
4+
5+
BASE_DIR=$(dirname "${BASH_SOURCE[0]:-$0}")
6+
cd "${BASE_DIR}/.." || exit 127
7+
8+
# shellcheck source=../scripts/helpers.sh
9+
. scripts/helpers.sh
10+
# shellcheck source=../scripts/logging.sh
11+
. scripts/logging.sh
12+
# shellcheck source=../scripts/utils.sh
13+
. scripts/utils.sh
14+
15+
PROGRAM=$(basename "${BASH_SOURCE[0]:-$0}")
16+
VERSION=0.5.5
17+
18+
function display_help() {
19+
cat <<EOF
20+
$(help_title_section Usage)
21+
${PROGRAM} [options] <command>
22+
23+
$(help_title_section Commands)
24+
prod Deploy to production environment.
25+
stg Deploy to staging environment [default command].
26+
27+
$(help_title_section Options)
28+
-h --help Show this screen.
29+
-v --version Show version.
30+
EOF
31+
}
32+
33+
function deploy() {
34+
local file
35+
local env
36+
37+
env=${1:-stg}
38+
file="fly-${env}.toml"
39+
40+
if ! command -v flyctl &>/dev/null; then
41+
log_error "flyctl is not installed. Please install it and authenticate."
42+
exit 1
43+
fi
44+
45+
fly deploy --config "${file}"
46+
47+
log_success "Successfully deployed ${env} to fly.io"
48+
}
49+
50+
case ${1:-stg} in
51+
-h | --help)
52+
display_help
53+
;;
54+
-v | --version)
55+
display_version "${VERSION}" "${PROGRAM}"
56+
;;
57+
prod | stg)
58+
deploy "${1:-stg}"
59+
;;
60+
*)
61+
display_help >&2
62+
exit 1
63+
;;
64+
esac

bin/remote

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
3+
set -Eeuo pipefail
4+
5+
BASE_DIR=$(dirname "${BASH_SOURCE[0]:-$0}")
6+
cd "${BASE_DIR}/.." || exit 127
7+
8+
# shellcheck source=../scripts/helpers.sh
9+
. scripts/helpers.sh
10+
# shellcheck source=../scripts/logging.sh
11+
. scripts/logging.sh
12+
# shellcheck source=../scripts/utils.sh
13+
. scripts/utils.sh
14+
15+
PROGRAM=$(basename "${BASH_SOURCE[0]:-$0}")
16+
VERSION=0.5.5
17+
18+
function display_help() {
19+
cat <<EOF
20+
$(help_title_section Usage)
21+
${PROGRAM} [options] <command>
22+
23+
$(help_title_section Commands)
24+
prod Start production remote console.
25+
stg Start staging remote console [default command].
26+
27+
$(help_title_section Options)
28+
-h --help Show this screen.
29+
-v --version Show version.
30+
EOF
31+
}
32+
33+
function start_remote_console() {
34+
local file
35+
36+
env=${1:-stg}
37+
file="fly-${env}.toml"
38+
39+
export TERM="xterm-256color"
40+
41+
if ! command -v flyctl &>/dev/null; then
42+
log_error "flyctl is not installed. Please install it and authenticate."
43+
exit 1
44+
fi
45+
46+
fly --config "${file}" ssh console --pty -C "/app/bin/safira remote"
47+
}
48+
49+
case ${1:-stg} in
50+
-h | --help)
51+
display_help
52+
;;
53+
-v | --version)
54+
display_version "${VERSION}" "${PROGRAM}"
55+
;;
56+
prod | stg)
57+
start_remote_console "${1:-stg}"
58+
;;
59+
*)
60+
display_help >&2
61+
exit 1
62+
;;
63+
esac

config/config.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import Config
1010
config :safira,
1111
ecto_repos: [Safira.Repo],
1212
generators: [timestamp_type: :utc_datetime],
13+
from_email_name: System.get_env("FROM_EMAIL_NAME") || "SEI",
14+
from_email_address: System.get_env("FROM_EMAIL_ADDRESS") || "no-reply@seium.org",
1315
umami_script_url: System.get_env("UMAMI_SCRIPT_URL") || "",
1416
umami_website_id: System.get_env("UMAMI_WEBSITE_ID") || ""
1517

config/prod.exs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
import Config
22

3-
# Note we also include the path to a cache manifest
4-
# containing the digested version of static files. This
5-
# manifest is generated by the `mix assets.deploy` task,
6-
# which you should run after static files are built and
7-
# before starting your production server.
8-
config :safira, SafiraWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
3+
config :logger, :console, format: "[$level] $message\n"
94

10-
# Configures Swoosh API Client
11-
config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: Safira.Finch
5+
config :waffle,
6+
storage: Waffle.Storage.S3,
7+
bucket: {:system, "AWS_S3_BUCKET"},
8+
asset_host: {:system, "ASSET_HOST"}
129

13-
# Disable Swoosh Local Memory Storage
14-
config :swoosh, local: false
10+
config :ex_aws,
11+
json_codec: Jason,
12+
access_key_id: {:system, "AWS_ACCESS_KEY_ID"},
13+
secret_access_key: {:system, "AWS_SECRET_ACCESS_KEY"},
14+
region: {:system, "AWS_REGION"},
15+
s3: [
16+
scheme: "https://",
17+
host: {:system, "ASSET_HOST"},
18+
region: {:system, "AWS_REGION"},
19+
access_key_id: {:system, "AWS_ACCESS_KEY_ID"},
20+
secret_access_key: {:system, "AWS_SECRET_ACCESS_KEY"}
21+
]
1522

16-
# Do not print debug messages in production
17-
config :logger, level: :info
23+
config :swoosh, :api_client, Swoosh.ApiClient.Hackney
1824

19-
# Runtime production configuration, including reading
20-
# of environment variables, is done on config/runtime.exs.
25+
config :safira, Safira.Mailer, adapter: Swoosh.Adapters.ExAwsAmazonSES
26+
27+
# ## SSL Support
28+
#
29+
# To get SSL working, you will need to add the `https` key
30+
# to the previous section and set your `:url` port to 443:
31+
#
32+
config :safira, Safira.Endpoint,
33+
url: [scheme: "https", host: System.get_env("PHX_HOST") || "stg.seium.org", port: 443],
34+
force_ssl: [rewrite_on: [:x_forwarded_proto]],
35+
cache_static_manifest: "priv/static/cache_manifest.json"

0 commit comments

Comments
 (0)