elektrine/deploy/docker/Dockerfile
maxfield da169a3869
Some checks failed
Deploy Docker Images / Build, push, and deploy (push) Failing after 23m0s
feat(privacy): harden session logs, admin access, and docker isolation
Disable durable VPN session logs and never store client IPs. Drop admin
body decrypt and impersonation. Default mail privacy (no raw copy, no
inbound MTA IP, trash/spam retention). Tighten compose caps and split
edge/db networks; gate Tor/VPN packages by release modules. Update legal
copy and operator hardening docs.
2026-08-02 22:55:08 -04:00

274 lines
9.1 KiB
Docker

# Find eligible builder and runner images on Docker Hub. We use Debian
# instead of Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=debian
# https://hub.docker.com/_/debian?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/_/ubuntu - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.19.5-erlang-28.0-ubuntu-noble-20260324
#
ARG ELIXIR_VERSION=1.19.5
ARG OTP_VERSION=28.1.1
ARG UBUNTU_VERSION=noble-20260324
ARG RELEASE_NAME=elektrine
ARG ELEKTRINE_RELEASE_MODULES=all
ARG PRIMARY_DOMAIN=example.com
ARG EMAIL_DOMAIN=example.com
ARG SUPPORTED_DOMAINS=example.com
ARG PROFILE_BASE_DOMAINS=example.com
ARG NPM_VERSION=11.13.0
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-ubuntu-${UBUNTU_VERSION}@sha256:1486c932fb7576bea9becaaa33da9946205161a2d16938a3351c455d59ce6c3a"
ARG RUNNER_IMAGE="ubuntu:${UBUNTU_VERSION}@sha256:84e77dee7d1bc93fb029a45e3c6cb9d8aa4831ccfcc7103d36e876938d28895b"
FROM ${BUILDER_IMAGE} AS builder
ARG RELEASE_NAME
ARG ELEKTRINE_RELEASE_MODULES
ARG PRIMARY_DOMAIN
ARG EMAIL_DOMAIN
ARG SUPPORTED_DOMAINS
ARG PROFILE_BASE_DOMAINS
ARG NPM_VERSION
# Retry helper function for flaky network operations
SHELL ["/bin/bash", "-c"]
# install build dependencies with retry
RUN for i in 1 2 3 4 5; do \
if apt-get update -y && \
apt-get install -y build-essential git libvips-dev; then \
apt-get clean; \
rm -rf /var/lib/apt/lists/*; \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
# prepare build dir
WORKDIR /app
# install hex + rebar with retry
RUN for i in 1 2 3 4 5; do \
if mix local.hex --force && \
mix local.rebar --force; then \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
# set build ENV
ENV MIX_ENV="prod"
ENV ELEKTRINE_RELEASE_MODULES="${ELEKTRINE_RELEASE_MODULES}"
ENV PRIMARY_DOMAIN="${PRIMARY_DOMAIN}"
ENV EMAIL_DOMAIN="${EMAIL_DOMAIN}"
ENV SUPPORTED_DOMAINS="${SUPPORTED_DOMAINS}"
ENV PROFILE_BASE_DOMAINS="${PROFILE_BASE_DOMAINS}"
ENV NPM_CONFIG_UPDATE_NOTIFIER=false
# install mix dependencies
COPY mix.lock ./
COPY release_builder/mix.exs release_builder/mix.exs
COPY release_builder/module_selection.exs release_builder/module_selection.exs
COPY release_builder/config/config.exs release_builder/config/config.exs
COPY release_builder/config/runtime.exs release_builder/config/runtime.exs
COPY apps/elektrine/mix.exs apps/elektrine/mix.exs
COPY apps/arblarg/mix.exs apps/arblarg/mix.exs
COPY apps/atomine/mix.exs apps/atomine/mix.exs
COPY apps/kairo/mix.exs apps/kairo/mix.exs
COPY apps/paige/mix.exs apps/paige/mix.exs
COPY apps/elektrine_web/mix.exs apps/elektrine_web/mix.exs
COPY apps/elektrine_email/mix.exs apps/elektrine_email/mix.exs
COPY apps/elektrine_social/mix.exs apps/elektrine_social/mix.exs
COPY apps/elektrine_dns/mix.exs apps/elektrine_dns/mix.exs
COPY apps/elektrine_nerve/mix.exs apps/elektrine_nerve/mix.exs
COPY apps/elektrine_vpn/mix.exs apps/elektrine_vpn/mix.exs
COPY apps/elektrine_uptime/mix.exs apps/elektrine_uptime/mix.exs
COPY config/config.exs config/prod.exs config/
WORKDIR /app/release_builder
RUN for i in 1 2 3 4 5; do \
if mix deps.get --only $MIX_ENV; then \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
RUN mix deps.compile
WORKDIR /app
COPY apps apps
COPY release_builder release_builder
COPY config/runtime.exs config/runtime.exs
COPY config/runtime config/runtime
COPY --chmod=755 scripts/release/deploy_release.sh scripts/release/deploy_release.sh
COPY scripts/lib scripts/lib
# Install Node.js 20.x (required for Tailwind v4)
RUN for i in 1 2 3 4 5; do \
if apt-get update -y && \
apt-get install -y ca-certificates curl gnupg && \
mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
apt-get update -y && \
apt-get install -y nodejs; then \
apt-get clean; \
rm -rf /var/lib/apt/lists/*; \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
# Update npm before installing asset dependencies.
RUN for i in 1 2 3 4 5; do \
if npm install -g npm@"${NPM_VERSION}"; then \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
# Install npm dependencies for assets with retry (includes tailwindcss)
RUN for i in 1 2 3 4 5; do \
if (cd apps/elektrine/assets && npm ci); then \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
# Install esbuild with retry (tailwind is now installed via npm)
RUN for i in 1 2 3 4 5; do \
if (cd release_builder && mix esbuild.install); then \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
# Build the selected hoster release through release_builder/
RUN ./scripts/release/deploy_release.sh --release-name "${RELEASE_NAME}" --mix-env "${MIX_ENV}"
# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}
ARG RELEASE_NAME
ARG ELEKTRINE_RELEASE_MODULES
# Optional overrides: true|false|auto (auto derives from ELEKTRINE_RELEASE_MODULES).
ARG INSTALL_TOR=auto
ARG INSTALL_VPN_TOOLS=auto
SHELL ["/bin/bash", "-c"]
# Core runtime packages always. Tor / WireGuard / Shadowsocks only when the
# release modules (or explicit build args) need them — keeps slim images smaller.
RUN set -euo pipefail; \
modules="${ELEKTRINE_RELEASE_MODULES:-all}"; \
install_tor="${INSTALL_TOR:-auto}"; \
install_vpn="${INSTALL_VPN_TOOLS:-auto}"; \
if [ "$install_tor" = "auto" ]; then \
case ",${modules}," in \
*,all,*|*,tor,*) install_tor=true ;; \
*) install_tor=false ;; \
esac; \
fi; \
if [ "$install_vpn" = "auto" ]; then \
case ",${modules}," in \
*,all,*|*,vpn,*) install_vpn=true ;; \
*) install_vpn=false ;; \
esac; \
fi; \
pkgs="libstdc++6 openssl libncurses6 ca-certificates curl libvips42 gpg gnupg2 dirmngr"; \
if [ "$install_tor" = "true" ] || [ "$install_tor" = "1" ]; then \
pkgs="$pkgs tor"; \
fi; \
if [ "$install_vpn" = "true" ] || [ "$install_vpn" = "1" ]; then \
pkgs="$pkgs wireguard-tools iproute2 shadowsocks-libev"; \
fi; \
echo "Runner packages (modules=${modules} tor=${install_tor} vpn=${install_vpn}): $pkgs"; \
for i in 1 2 3 4 5; do \
if apt-get update -y && apt-get install -y $pkgs; then \
apt-get clean; \
rm -rf /var/lib/apt/lists/*; \
break; \
fi; \
if [ "$i" = "5" ]; then \
echo "Retry $i failed, giving up." >&2; \
exit 1; \
fi; \
echo "Retry $i failed, waiting..."; \
sleep 5; \
done
RUN which gpg && gpg --version
# Setup data directories (will be mounted as volume in production)
RUN mkdir -p /data/tor/elektrine /data/tor/data /data/certs && \
chown -R nobody:nogroup /data
# Tor config is harmless when tor is not installed; start.sh only launches tor
# when the runtime role/profile needs it.
COPY --chmod=644 deploy/onion/torrc /etc/tor/torrc
# Use a built-in UTF-8 locale available on Ubuntu images
ENV LANG=C.UTF-8
ENV LANGUAGE=C.UTF-8
ENV LC_ALL=C.UTF-8
WORKDIR "/app"
RUN chown nobody /app
# Copy startup scripts
COPY --chmod=755 deploy/docker/start.sh /app/start.sh
COPY --chmod=755 deploy/docker/docker-entrypoint.sh /app/docker-entrypoint.sh
COPY --chmod=755 scripts/acme /app/scripts/acme
# set runner ENV
ENV MIX_ENV="prod"
ENV PATH="/usr/bin:$PATH"
ENV RELEASE_NAME="${RELEASE_NAME}"
ENV ELEKTRINE_RELEASE_MODULES="${ELEKTRINE_RELEASE_MODULES}"
# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_deploy_release/${RELEASE_NAME} ./
# Expose ports for HTTP and HTTPS (non-privileged ports, fly.toml maps external 80/443)
EXPOSE 8080 8443
CMD ["/app/docker-entrypoint.sh"]