-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
117 lines (103 loc) · 5.57 KB
/
Copy pathDockerfile
File metadata and controls
117 lines (103 loc) · 5.57 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
# Not distroless -- sh is needed for docker exec and for HEALTHCHECK's CMD-SHELL.
FROM denoland/deno:alpine AS base
RUN apk -U upgrade --no-cache
WORKDIR /usr/src/app
# Build-time base, Debian. @inlang/paraglide-js 2.24 pulls @inlang/sdk 3, which
# replaced the WASM SQLite in @lix-js/sdk with a native addon. Its prebuilt
# binaries are glibc-only -- darwin-arm64, linux-arm64, linux-x64, win32-x64, no
# musl -- so under Alpine `paraglide-js compile` cannot load it and the build
# dies on a missing ld-linux-x86-64.so.2. gcompat does not rescue it: the loader
# then resolves, but relocation fails on __isoc23_strtoull, a glibc 2.38 symbol
# gcompat does not implement.
FROM node:26 AS build-base
WORKDIR /usr/src/app
ENV MISE_DATA_DIR=/mise \
MISE_CONFIG_DIR=/mise \
MISE_CACHE_DIR=/mise/cache \
MISE_INSTALL_PATH=/usr/local/bin/mise \
MISE_TRUSTED_CONFIG_PATHS=/usr/src/app \
PATH=/mise/shims:$PATH
COPY mise.toml /mise/config.toml
RUN curl https://mise.run | sh && mise install aube
# The .npmrc build jail wraps dependency scripts with Landlock and seccomp, and
# aube fails a script outright when the kernel cannot enforce them rather than
# run it unjailed. Builder kernels do not always ship Landlock, and the build
# container already confines these scripts, so drop the jail for image builds
# and keep it for dev installs. Stage-wide because aubr installs again before
# running scripts, so the builder stage jails too, not just the install stage.
ENV AUBE_JAIL_BUILDS=false
# Materialize the virtual store inside node_modules instead of symlinking into
# the per-user store at ~/.cache/aube/virtual-store, so the install stage's
# tree survives the COPY into the builder stage and aubr's up-to-date check
# passes there instead of re-downloading every package. Set through the
# environment: the --disable-global-virtual-store flag parses but does not
# apply the setting.
ENV AUBE_ENABLE_GLOBAL_VIRTUAL_STORE=false
# Install dependencies into a temp directory once, for the builder's use only.
# Nothing from this tree reaches the runtime image: the SSR bundle inlines every
# dependency it needs, so the runner stage ships no node_modules at all.
FROM build-base AS install
RUN mkdir -p /temp/deps
COPY package.json aube-lock.yaml .npmrc /temp/deps/
WORKDIR /temp/deps
RUN aube ci
# Stage 2: Build application
FROM build-base AS builder
WORKDIR /usr/src/app
COPY --from=install /temp/deps/node_modules node_modules
COPY . .
# VITE_ prefixed vars are client-side and must be set at build time
ARG VITE_BETTER_AUTH_URL="https://localhost:3000"
ENV VITE_BETTER_AUTH_URL=$VITE_BETTER_AUTH_URL
# Railway has no docker build-secret support, so the real secret lands in this stage's build cache
# and logs. It does not reach the runtime image (the runner stage is separate) and is not baked
# into the bundle — auth.ts reads process.env at runtime.
# Track: https://station.railway.com/feedback/support-docker-build-secrets-0b8787b2
ARG BETTER_AUTH_SECRET
ENV BETTER_AUTH_SECRET=$BETTER_AUTH_SECRET
RUN aubr prepare && aubr build:all
# Stage 3: Production image
FROM base AS runner
# Static OCI metadata, so an image built outside the pipeline — by a self-hoster from
# source, or by Railway — is still self-describing. CI overrides these and adds the
# dynamic ones (revision, version, created) via docker/metadata-action.
LABEL org.opencontainers.image.title="uppity" \
org.opencontainers.image.description="Self-hosted uptime monitoring and status pages. HTTP, TCP, and push-based health checks with incident tracking and multi-channel notifications." \
org.opencontainers.image.source="https://github.com/NeonRook/uppity" \
org.opencontainers.image.url="https://github.com/NeonRook/uppity" \
org.opencontainers.image.documentation="https://github.com/NeonRook/uppity#readme" \
org.opencontainers.image.licenses="AGPL-3.0-only" \
org.opencontainers.image.vendor="NeonRook"
# Create non-root user
# busybox applets, so short flags only: -S system, -u uid, -g gid, -G group.
RUN addgroup -S -g 1001 uppity && \
adduser -S -u 1001 -G uppity uppity
COPY --from=builder --chown=uppity:uppity /usr/src/app/.deno-deploy ./.deno-deploy
COPY --from=builder --chown=uppity:uppity /usr/src/app/build ./build
# Migration SQL. scripts/migrate.ts hardcodes ./drizzle; drizzle.config.ts is
# drizzle-kit's config and drizzle-kit is not in this image, so it is not copied.
COPY --from=builder --chown=uppity:uppity /usr/src/app/drizzle ./drizzle
# Derives each process's Deno permission set from the environment and execs it.
# Taken from the build context rather than the builder stage: it is shipped
# as-is and never passes through the bundler.
COPY --chown=uppity:uppity scripts/entrypoint.sh ./entrypoint.sh
USER uppity
EXPOSE 3000/tcp
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
# busybox wget, not `deno eval`. eval runs with implicit access to every
# permission and rejects the permission flags outright, so a probe written that
# way would be the one process in the image still running unrestricted, every
# thirty seconds. wget exits non-zero on a non-2xx status, which is the whole
# job. Shell form, so PORT expands.
#
# Workers run the same image but override CMD and don't serve HTTP, so this
# probe will fail for them — disable the healthcheck on worker containers in
# your deployment config.
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget -q -O /dev/null "http://127.0.0.1:${PORT}/api/health"
# Default to web server, override for workers:
# docker run ... [image] ./entrypoint.sh worker-monitor
# docker run ... [image] ./entrypoint.sh worker-notifier
CMD ["./entrypoint.sh", "serve"]