Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
134 lines (110 loc) · 5.35 KB
/
Copy pathDockerfile
File metadata and controls
134 lines (110 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# syntax=docker/dockerfile:1.7
# =============================================================================
# AllSource Query Service - Production-Optimized Multi-Stage Dockerfile
# =============================================================================
#
# Optimizations applied:
# - Runtime uses plain alpine instead of elixir image (~180MB -> ~8MB base)
# - Elixir release includes ERTS, no full Elixir installation needed at runtime
# - Migrations included for Ecto release tasks
# - Healthcheck uses release RPC instead of wget (removes wget dependency)
# - Combined RUN commands to reduce layers
# =============================================================================
ARG ELIXIR_VERSION=1.18
ARG OTP_VERSION=27
# Must match Alpine version in elixir:${ELIXIR_VERSION}-otp-${OTP_VERSION}-alpine for OpenSSL compatibility
ARG ALPINE_VERSION=3.23
# =============================================================================
# Stage 1: Build
# =============================================================================
FROM elixir:${ELIXIR_VERSION}-otp-${OTP_VERSION}-alpine AS build
ARG VERSION=dev
ARG REVISION=unknown
# Install build dependencies and hex/rebar in single layer
RUN apk add --no-cache build-base git cmake && \
mix local.hex --force && \
mix local.rebar --force
WORKDIR /app
ARG ALLSOURCE_EDITION=community
# Set build environment.
# ALLSOURCE_EDITION is exported as ENV so it is visible to `mix compile` below:
# config.exs reads it at compile time and the router gates enterprise-only route
# scopes (analytics, team, audit) behind a module-level `if enterprise?()`. An
# enterprise image MUST compile with this set or those routes are stripped from the
# release and return 404 at runtime even when ALLSOURCE_EDITION=enterprise is set.
ENV MIX_ENV=prod \
ALLSOURCE_EDITION=${ALLSOURCE_EDITION} \
SECRET_KEY_BASE=build_time_dummy_key_will_be_replaced_at_runtime_minimum_64_chars
# Copy mix files first for dependency caching
COPY mix.exs mix.lock ./
# Install and compile dependencies (cached layer)
RUN mix deps.get --only prod && \
mix deps.compile
# Copy source code and priv directory (includes migrations)
COPY lib ./lib
COPY config ./config
COPY priv ./priv
# Compile and build release
RUN mix compile && \
mix release
# =============================================================================
# Stage 2: Runtime
# =============================================================================
# Use plain alpine - Elixir releases are self-contained with ERTS included.
# This reduces image size from ~180MB (elixir-alpine) to ~8MB (alpine base).
# The release at _build/prod/rel/query_service_ex includes everything needed.
# =============================================================================
FROM alpine:${ALPINE_VERSION} AS runtime
ARG VERSION=dev
ARG REVISION=unknown
ARG BUILDTIME=unknown
# OCI Image Labels
LABEL org.opencontainers.image.title="AllSource Query Service" \
org.opencontainers.image.description="High-performance query service for AllSource event store" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${REVISION}" \
org.opencontainers.image.created="${BUILDTIME}" \
org.opencontainers.image.vendor="AllSource Team" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.source="https://github.com/all-source-os/allsource-monorepo"
# Install minimal runtime dependencies and create non-root user in single layer
# - tini: proper signal handling and zombie reaping
# - libstdc++: required by ERTS for C++ standard library
# - libgcc: required by ERTS for GCC runtime
# - ncurses-libs: required by ERTS for terminal handling
# - libcrypto3: required by ERTS crypto module (OpenSSL)
RUN apk add --no-cache tini libstdc++ libgcc ncurses-libs libcrypto3 && \
rm -rf /var/cache/apk/* /tmp/* && \
addgroup -g 1000 -S app && \
adduser -u 1000 -S app -G app -h /app -s /sbin/nologin
WORKDIR /app
# Copy release from build stage
COPY --from=build --chown=app:app /app/_build/prod/rel/query_service_ex ./
USER app
# Expose port - matches prod.exs and runtime.exs default of 3902
EXPOSE 3902
# Environment configuration
# Required environment variables (must be provided at runtime):
# DATABASE_URL - PostgreSQL connection URL (ecto://USER:PASS@HOST/DATABASE)
# SECRET_KEY_BASE - Secret key for signing (generate with: mix phx.gen.secret)
#
# Optional environment variables:
# PORT - HTTP port (default: 3902)
# POOL_SIZE - Database pool size (default: 10)
# PHX_HOST - Hostname for URLs (default: localhost)
# CORE_URL - Core service URL (default: http://localhost:3900)
# CORE_WS_URL - Rust Core WebSocket URL (default: ws://localhost:3900)
ARG ALLSOURCE_EDITION=community
ENV HOME=/app \
MIX_ENV=prod \
PORT=3902 \
ALLSOURCE_EDITION=${ALLSOURCE_EDITION} \
ERL_FLAGS="+sbwt very_short +sbwtdcpu very_short +sbwtdio very_short" \
MALLOC_ARENA_MAX=2
# Health check using release RPC - no wget needed
# This calls the release's built-in RPC mechanism to verify the VM is responsive
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD bin/query_service_ex rpc "IO.puts(:ok)" | grep -q "ok" || exit 1
# Use tini for proper signal handling
ENTRYPOINT ["/sbin/tini", "-g", "--"]
CMD ["bin/query_service_ex", "start"]