Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
274 lines (243 loc) · 16.7 KB
/
Copy pathDockerfile
File metadata and controls
274 lines (243 loc) · 16.7 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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# syntax=docker/dockerfile:1.7@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e
FROM node:26-bookworm@sha256:fa52d1669505e29b58c042875b1d65c229e0787a3610fd52869b22b68e480853 AS node
# A caller may override this empty stage with a named build context containing
# basemap.pmtiles. The generator verifies the pinned digest before accepting it.
FROM scratch AS mapassetseed
FROM golang:1.27.4-bookworm@sha256:4fa7c968ba93f1635670522dbb01a2c78150443e1385746924566079f36570ea AS go-deps
WORKDIR /src
COPY go.mod go.sum ./
COPY pkg/apigen/go.mod pkg/apigen/go.sum ./pkg/apigen/
RUN go mod download || \
(sleep 5 && go mod download) || \
(sleep 5 && go mod download)
FROM go-deps AS sourcegen
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
COPY . .
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,id=leapview-go-mod,target=/go/pkg/mod,from=go-deps,source=/go/pkg/mod,sharing=locked \
./scripts/generate_build_sources.sh && \
./scripts/time_build_phase.sh cli-docs go run ./internal/app/tools/clidocgen && \
./scripts/time_build_phase.sh schema-docs go run ./internal/app/tools/schemadocgen && \
./scripts/time_build_phase.sh openapi-docs go run ./internal/app/tools/openapidocgen && \
./scripts/time_build_phase.sh docs-site go run ./internal/app/tools/docsitegen
# Keep the large, network-backed map extraction separate so a transient remote
# failure can be retried without repeating deterministic source generation.
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,id=leapview-go-mod,target=/go/pkg/mod,from=go-deps,source=/go/pkg/mod,sharing=locked \
--mount=type=bind,from=mapassetseed,source=/,target=/mapasset-seed,ro \
if [ -f /mapasset-seed/basemap.pmtiles ]; then \
go run ./internal/app/tools/mapassets --out .data/map-assets --seed-archive /mapasset-seed/basemap.pmtiles; \
else \
go run ./internal/app/tools/mapassets --out .data/map-assets; \
fi
FROM oven/bun:1.4.4@sha256:1d22548f177f4ff38e0c15baa3c2c46c6c2060ba2d1bc573bb4720b407548f1d AS web
WORKDIR /src
COPY --from=go-deps /usr/local/go/bin/gofmt /usr/local/bin/gofmt
COPY package.json bun.lock tsconfig.json ./
RUN bun install --frozen-lockfile --no-cache
COPY scripts ./scripts
COPY static ./static
COPY web ./web
COPY --from=sourcegen /src/api/gen ./api/gen
COPY --from=sourcegen /src/api/visualization ./api/visualization
COPY --from=sourcegen /src/web/generated ./web/generated
RUN mkdir -p internal/dashboard/appearance && \
bun scripts/generate_lucide_icon_catalog.ts && \
bun scripts/generate_visualization_validator.ts && \
bun run build
FROM go-deps AS build
ARG BUILD_VERSION=development
ARG BUILD_REVISION=unknown
ARG BUILD_TIME=unknown
ARG BUILD_DIRTY=true
ARG BUILD_RELEASE=false
COPY . .
COPY --from=sourcegen /src/api/gen ./api/gen
COPY --from=sourcegen /src/internal/access/api/gen ./internal/access/api/gen
COPY --from=sourcegen /src/internal/agent/api/gen ./internal/agent/api/gen
COPY --from=sourcegen /src/internal/analytics/api/gen ./internal/analytics/api/gen
COPY --from=sourcegen /src/internal/credential/api/gen ./internal/credential/api/gen
COPY --from=sourcegen /src/internal/dashboard/api/gen ./internal/dashboard/api/gen
COPY --from=sourcegen /src/internal/deployment/api/gen ./internal/deployment/api/gen
COPY --from=sourcegen /src/internal/manageddata/api/gen ./internal/manageddata/api/gen
COPY --from=sourcegen /src/internal/app/api/aggregate ./internal/app/api/aggregate
COPY --from=sourcegen /src/internal/app/api/gen ./internal/app/api/gen
COPY --from=sourcegen /src/internal/platform/http/api/gen ./internal/platform/http/api/gen
COPY --from=sourcegen /src/internal/project/api/gen ./internal/project/api/gen
COPY --from=sourcegen /src/internal/refresh/api/gen ./internal/refresh/api/gen
COPY --from=sourcegen /src/internal/release/api/gen ./internal/release/api/gen
COPY --from=sourcegen /src/internal/app/cli/gen ./internal/app/cli/gen
COPY --from=sourcegen /src/internal/app/config/config_gen.go ./internal/app/config/config_gen.go
COPY --from=sourcegen /src/internal/app/config/spec/names_gen.go ./internal/app/config/spec/names_gen.go
# Every PostgreSQL sqlc package is generated in sourcegen and excluded from the
# build context, so copy each package into the build stage explicitly.
COPY --from=sourcegen /src/internal/project/postgres/internal/db ./internal/project/postgres/internal/db
COPY --from=sourcegen /src/internal/project/developmentsession/postgres/internal/db ./internal/project/developmentsession/postgres/internal/db
COPY --from=sourcegen /src/internal/access/postgres/internal/db ./internal/access/postgres/internal/db
COPY --from=sourcegen /src/internal/credential/postgres/internal/db ./internal/credential/postgres/internal/db
COPY --from=sourcegen /src/internal/admin/product/postgres/internal/db ./internal/admin/product/postgres/internal/db
COPY --from=sourcegen /src/internal/agent/postgres/internal/db ./internal/agent/postgres/internal/db
COPY --from=sourcegen /src/internal/dashboard/session/postgres/internal/db ./internal/dashboard/session/postgres/internal/db
COPY --from=sourcegen /src/internal/dashboard/usage/postgres/internal/db ./internal/dashboard/usage/postgres/internal/db
COPY --from=sourcegen /src/internal/dashboard/appearance/postgres/internal/db ./internal/dashboard/appearance/postgres/internal/db
COPY --from=sourcegen /src/internal/dashboard/authoring/postgres/internal/db ./internal/dashboard/authoring/postgres/internal/db
COPY --from=sourcegen /src/internal/dashboard/publication/postgres/internal/db ./internal/dashboard/publication/postgres/internal/db
COPY --from=sourcegen /src/internal/platform/operation/postgres/internal/db ./internal/platform/operation/postgres/internal/db
COPY --from=sourcegen /src/internal/platform/postgres/internal/db ./internal/platform/postgres/internal/db
COPY --from=sourcegen /src/internal/platform/bootstrap/postgres/internal/db ./internal/platform/bootstrap/postgres/internal/db
COPY --from=sourcegen /src/internal/platform/http/cursorsigning/postgres/internal/db ./internal/platform/http/cursorsigning/postgres/internal/db
COPY --from=sourcegen /src/internal/manageddata/postgres/internal/db ./internal/manageddata/postgres/internal/db
COPY --from=sourcegen /src/internal/servingstate/postgres/internal/db ./internal/servingstate/postgres/internal/db
COPY --from=sourcegen /src/internal/platform/events/postgres/internal/db ./internal/platform/events/postgres/internal/db
COPY --from=sourcegen /src/internal/platform/jobs/postgres/internal/db ./internal/platform/jobs/postgres/internal/db
COPY --from=sourcegen /src/internal/deployment/postgres/internal/db ./internal/deployment/postgres/internal/db
COPY --from=sourcegen /src/internal/lineage/postgres/internal/db ./internal/lineage/postgres/internal/db
COPY --from=sourcegen /src/internal/refresh/postgres/internal/db ./internal/refresh/postgres/internal/db
COPY --from=sourcegen /src/internal/analytics/physicalpool/postgres/internal/db ./internal/analytics/physicalpool/postgres/internal/db
COPY --from=sourcegen /src/internal/analytics/connectionbinding/postgres/internal/db ./internal/analytics/connectionbinding/postgres/internal/db
COPY --from=sourcegen /src/internal/analytics/ducklake/postgres/internal/db ./internal/analytics/ducklake/postgres/internal/db
COPY --from=sourcegen /src/internal/analytics/queryaudit/postgres/internal/db ./internal/analytics/queryaudit/postgres/internal/db
COPY --from=sourcegen /src/internal/analytics/exploration/saved/postgres/internal/db ./internal/analytics/exploration/saved/postgres/internal/db
COPY --from=sourcegen /src/internal/release/postgres/internal/db ./internal/release/postgres/internal/db
COPY --from=sourcegen /src/internal/recoveryset/postgres/internal/db ./internal/recoveryset/postgres/internal/db
COPY --from=sourcegen /src/internal/access/ui/signals/models.gen.go ./internal/access/ui/signals/models.gen.go
COPY --from=sourcegen /src/internal/admin/ui/signals/models.gen.go ./internal/admin/ui/signals/models.gen.go
COPY --from=sourcegen /src/internal/agent/ui/signals/models.gen.go ./internal/agent/ui/signals/models.gen.go
COPY --from=sourcegen /src/internal/dashboard/ui/signals/models.gen.go ./internal/dashboard/ui/signals/models.gen.go
COPY --from=web /src/internal/dashboard/appearance/icons_gen.go ./internal/dashboard/appearance/icons_gen.go
COPY --from=sourcegen /src/docs ./docs
COPY --from=sourcegen /src/schemas ./schemas
COPY --from=sourcegen /src/web/generated ./web/generated
COPY --from=web /src/static ./static
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,id=leapview-go-mod,target=/go/pkg/mod,from=go-deps,source=/go/pkg/mod,sharing=locked \
BUILD_LDFLAGS="-s -w \
-X github.com/flidai/leapview/internal/platform/buildinfo.version=${BUILD_VERSION} \
-X github.com/flidai/leapview/internal/platform/buildinfo.revision=${BUILD_REVISION} \
-X github.com/flidai/leapview/internal/platform/buildinfo.buildTime=${BUILD_TIME} \
-X github.com/flidai/leapview/internal/platform/buildinfo.dirty=${BUILD_DIRTY} \
-X github.com/flidai/leapview/internal/platform/buildinfo.release=${BUILD_RELEASE}" && \
CGO_ENABLED=1 go build -tags=duckdb_arrow -trimpath -ldflags="$BUILD_LDFLAGS" -o /out/leapview ./cmd/leapview && \
CGO_ENABLED=1 go build -tags=duckdb_arrow -trimpath -ldflags="$BUILD_LDFLAGS" -o /out/leapviewctl ./cmd/leapviewctl
# The production image carries a complete, target-native, offline extension
# supply. This stage performs the only upstream acquisition during packaging;
# the tool installs the exact pinned core artifacts, asks DuckDB to LOAD every
# absolute file with automatic install/load disabled, and emits a digested
# manifest consumed by the runtime image.
FROM build AS extension-supply
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,id=leapview-go-mod,target=/go/pkg/mod,from=go-deps,source=/go/pkg/mod,sharing=locked \
go run ./internal/app/tools/extensionsupply --out /out/extension-supply
# Keep the pinned distroless runtime, but apply Debian's fixed OpenSSL packages
# while the upstream image catches up. The package payload and control metadata
# are both checksum-verified inputs; this stage is not part of the final image.
FROM node AS runtime-security-update
ARG TARGETARCH
RUN set -eu; \
openssl_deb_version=3.5.7-1~deb13u3; \
case "$TARGETARCH" in \
amd64) \
libssl_sha256=ff16bc048bcd7d1b256094450b79c77947d8e76fe2a24bd99b91021d591fa074; \
provider_sha256=c12e0266c4780749a4702b8959ac979689f4182e18eed85a8a5eb90ed0e16eab ;; \
arm64) \
libssl_sha256=d0681293a160392186c6ef85a165e40603d1628a099936137d24d391bd591f97; \
provider_sha256=dae4ab96be3a1fc8fd490bbeb3938ecd102d360555d4af8ee2728b42ce9f1059 ;; \
*) printf 'unsupported runtime package architecture: %s\n' "$TARGETARCH" >&2; exit 1 ;; \
esac; \
mkdir -p /out/runtime-security/var/lib/dpkg/status.d /tmp/runtime-security-control; \
for package in libssl3t64 openssl-provider-legacy; do \
case "$package" in \
libssl3t64) checksum="$libssl_sha256" ;; \
openssl-provider-legacy) checksum="$provider_sha256" ;; \
esac; \
archive="/tmp/${package}.deb"; \
url="https://security.debian.org/debian-security/pool/updates/main/o/openssl/${package}_${openssl_deb_version}_${TARGETARCH}.deb"; \
curl --fail --location --proto '=https' --proto-redir '=https' --retry 4 --retry-all-errors --silent --show-error --output "$archive" "$url"; \
printf '%s %s\n' "$checksum" "$archive" | sha256sum --check --status || { printf 'checksum verification failed for %s\n' "$package" >&2; exit 1; }; \
dpkg-deb --extract "$archive" /out/runtime-security; \
control_dir="/tmp/runtime-security-control/$package"; \
dpkg-deb --control "$archive" "$control_dir"; \
test -f "$control_dir/control" && test -f "$control_dir/md5sums"; \
install -m 0644 "$control_dir/control" "/out/runtime-security/var/lib/dpkg/status.d/$package"; \
install -m 0644 "$control_dir/md5sums" "/out/runtime-security/var/lib/dpkg/status.d/$package.md5sums"; \
done
FROM gcr.io/distroless/cc-debian13:debug-nonroot@sha256:8b33d821344902366f6ac0fd2cabf4c39d5b98abe37ebc9eb8c35943f8ede923 AS runtime-base
COPY --from=runtime-security-update /out/runtime-security/ /
FROM runtime-base AS runtime
USER root
SHELL ["/busybox/sh", "-c"]
ARG BUILD_VERSION=development
ARG BUILD_REVISION=unknown
ARG BUILD_TIME=unknown
ARG BUILD_DIRTY=true
ARG BUILD_RELEASE=false
LABEL service="leapview" \
org.opencontainers.image.title="LeapView" \
org.opencontainers.image.description="LeapView business intelligence server" \
org.opencontainers.image.source="https://github.com/flidai/leapview" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="$BUILD_VERSION" \
org.opencontainers.image.revision="$BUILD_REVISION" \
org.opencontainers.image.created="$BUILD_TIME" \
dev.leapview.build.dirty="$BUILD_DIRTY" \
dev.leapview.build.release="$BUILD_RELEASE"
# The pinned distroless compatibility image supplies glibc, libstdc++, CA
# certificates, tzdata, and the BusyBox utilities used by release
# qualification without carrying a package manager or general-purpose Debian
# runtime utilities.
RUN printf '%s\n' 'leapview:x:999:' >> /etc/group && \
printf '%s\n' 'leapview:x:999:999::/var/lib/leapview:/sbin/nologin' >> /etc/passwd && \
test "$(id -u leapview)" = 999 && \
test "$(id -g leapview)" = 999 && \
for utility in sh env cat cp rm mkdir find du wc test stat readlink sha256sum tar gzip gunzip sync; do \
command -v "$utility" >/dev/null || exit 1; \
done
WORKDIR /app
COPY --from=build /out/leapview /usr/local/bin/leapview
COPY --from=build /out/leapviewctl /usr/local/libexec/leapviewctl
COPY --from=build /out/leapviewctl /usr/local/share/leapview/deployment/leapviewctl
COPY --from=extension-supply /out/extension-supply /usr/local/share/leapview/extensions
COPY deploy/compose/compose.yaml deploy/compose/compose.postgres.yaml deploy/compose/compose.https.yaml deploy/compose/compose.first-install-bootstrap.yaml deploy/compose/Caddyfile deploy/compose/Caddyfile.first-install-bootstrap deploy/compose/first-install.env deploy/compose/deployment.env.example deploy/compose/leapview.env.example deploy/compose/README.md deploy/compose/QUALIFICATION.md /usr/local/share/leapview/deployment/
COPY deploy/compose/postgres /usr/local/share/leapview/deployment/postgres
COPY deploy/compose/qualification /usr/local/share/leapview/deployment/qualification
COPY deploy/host/files/ /usr/local/share/leapview/deployment/
COPY --from=web /src/static ./static
COPY --from=build /src/schemas ./schemas
COPY --from=sourcegen /src/.data/map-assets ./.data/map-assets
COPY dashboards ./dashboards
COPY evaluation ./evaluation
RUN chmod 0500 /usr/local/share/leapview/deployment/leapviewctl \
/usr/local/share/leapview/deployment/leapviewctl-wrapper && \
find /usr/local/share/leapview/extensions -type d -exec chmod 0555 {} + && \
find /usr/local/share/leapview/extensions -type f -exec chmod 0444 {} + && \
chmod 0400 /usr/local/share/leapview/deployment/compose.yaml \
/usr/local/share/leapview/deployment/compose.postgres.yaml \
/usr/local/share/leapview/deployment/compose.https.yaml \
/usr/local/share/leapview/deployment/compose.first-install-bootstrap.yaml \
/usr/local/share/leapview/deployment/Caddyfile.first-install-bootstrap \
/usr/local/share/leapview/deployment/first-install.env \
/usr/local/share/leapview/deployment/Caddyfile \
/usr/local/share/leapview/deployment/deployment.env.example \
/usr/local/share/leapview/deployment/leapview.env.example \
/usr/local/share/leapview/deployment/README.md \
/usr/local/share/leapview/deployment/QUALIFICATION.md \
/usr/local/share/leapview/deployment/qualification/* && \
chmod 0444 /usr/local/share/leapview/deployment/postgres/*.sh && \
mkdir -p /var/lib/leapview/home && \
chown -R leapview:leapview /var/lib/leapview /app
USER leapview:leapview
ENV LEAPVIEW_ADDR=:8080 \
LEAPVIEW_ENVIRONMENT=prod \
LEAPVIEW_HOME=/var/lib/leapview/home \
LEAPVIEW_MAP_ASSET_DIR=/app/.data/map-assets \
LEAPVIEW_MANAGED_DATA_DIR=/var/lib/leapview/home/managed-data \
LEAPVIEW_DUCKDB_EXTENSION_SUPPLY_PATH=/usr/local/share/leapview/extensions/extension-supply.json \
LEAPVIEW_PRODUCTION=1
EXPOSE 8080
VOLUME ["/var/lib/leapview"]
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 CMD ["leapview", "healthcheck"]
ENTRYPOINT ["leapview"]
CMD ["serve", "--production"]