-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile.fast-sim
More file actions
160 lines (138 loc) · 7.64 KB
/
Copy pathDockerfile.fast-sim
File metadata and controls
160 lines (138 loc) · 7.64 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
#
# Brno University of Technology
# Faculty of Information Technology
#
# BSc Thesis 2006/2007
#
# Railway Interlocking Simulator
#
# fast-sim: native linuxX64 CLI binary
# Multi-stage build: Ubuntu 24.04 LTS (Noble) builder → Alpine runtime
#
# Usage:
# docker compose build fast-sim
# docker compose run fast-sim example shuntingLoop 60
#
# syntax=docker/dockerfile:1.4
# ============================================
# Stage 1: Build native binary with Gradle
# ============================================
# Eclipse Temurin provides JDK 21 on Ubuntu 24.04 LTS (Noble).
# We pin to the -noble tag instead of the rolling 21-jdk tag because the
# rolling tag now resolves to Ubuntu 26.04 (Resolute), whose libxml2 has
# SONAME libxml2.so.16 — incompatible with alpine:3.21's libxml2.so.2.
FROM --platform=linux/amd64 eclipse-temurin:21-jdk-noble AS builder
# GitHub Packages authentication is provided at build time via BuildKit secrets
# (see docker-compose.yml secrets: github_actor / github_token). The credentials
# are mounted under /run/secrets/ and exported as environment variables only
# inside the RUN steps that invoke Gradle. They are never interpolated into the
# Dockerfile command string, so they cannot leak into image history or build logs.
# Install git and native build dependencies
# libxml2-dev: cinterop headers for Kotlin/Native libxml2 bindings
# libicu-dev: transitive dependency of libxml2-dev on Debian/Ubuntu
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
libxml2-dev \
libicu-dev \
&& rm -rf /var/lib/apt/lists/* \
&& ln -sf /usr/include/unicode /usr/include/libxml2/unicode
# Create an unprivileged builder user so the native build runs as a normal user.
# Cache mounts, Gradle home, and the project directory are owned by this user.
# Use UID/GID 1001 to avoid colliding with the base image's ubuntu group (1000).
ARG BUILDER_UID=1001
ARG BUILDER_GID=1001
RUN (getent group ${BUILDER_GID} || groupadd --gid ${BUILDER_GID} builder) \
&& (getent passwd builder || useradd --uid ${BUILDER_UID} --gid ${BUILDER_GID} --shell /bin/bash --create-home builder) \
&& mkdir -p /build /home/builder/.gradle /home/builder/.m2 \
&& chown -R builder:builder /build /home/builder
ENV HOME=/home/builder
ENV GRADLE_USER_HOME=/home/builder/.gradle
WORKDIR /build
# Layer 1: Copy Gradle wrapper files (cached until wrapper version changes)
COPY --chown=builder:builder gradlew /build/
COPY --chown=builder:builder gradlew.bat /build/
COPY --chown=builder:builder gradle/ /build/gradle/
RUN chmod +x gradlew
# Layer 2: Copy build configuration files (cached until config changes)
COPY --chown=builder:builder settings.gradle.kts /build/
COPY --chown=builder:builder gradle.properties /build/
COPY --chown=builder:builder build.gradle.kts /build/
COPY --chown=builder:builder detekt.yml /build/
COPY --chown=builder:builder detekt-strict.yml /build/
COPY --chown=builder:builder .editorconfig /build/
COPY --chown=builder:builder core/build.gradle.kts /build/core/
COPY --chown=builder:builder core-test/build.gradle.kts /build/core-test/
COPY --chown=builder:builder desktop-ui/build.gradle.kts /build/desktop-ui/
COPY --chown=builder:builder fast-sim/build.gradle.kts /build/fast-sim/
# All subsequent Gradle commands run as the builder user.
USER builder
# Layer 2.5: kDisco artifacts are fetched from GitHub Packages during resolution.
# Credentials come from BuildKit secret mounts, not from ARG, so the token is
# never visible in image history or in BuildKit's resolved RUN command output.
# Layer 3: Resolve dependencies with BuildKit cache mount
RUN --mount=type=cache,target=/home/builder/.gradle/caches,id=fast-sim-gradle,uid=1001,gid=1001 \
--mount=type=cache,target=/home/builder/.gradle/wrapper,id=fast-sim-wrapper,uid=1001,gid=1001 \
--mount=type=cache,target=/home/builder/.m2/repository,id=fast-sim-m2,uid=1001,gid=1001 \
--mount=type=secret,id=github_actor,uid=1001,gid=1001,mode=0400 \
--mount=type=secret,id=github_token,uid=1001,gid=1001,mode=0400 \
GITHUB_ACTOR="$(cat /run/secrets/github_actor)" \
GITHUB_TOKEN="$(cat /run/secrets/github_token)" \
./gradlew dependencies --no-daemon --warning-mode=summary
# Layer 4: Copy source code (changes most frequently)
COPY --chown=builder:builder core/src/ /build/core/src/
COPY --chown=builder:builder core-test/src/ /build/core-test/src/
COPY --chown=builder:builder fast-sim/src/ /build/fast-sim/src/
# desktop-ui source needed for Gradle configuration resolution (settings.gradle.kts includes it)
# but not compiled — only an empty placeholder is required.
RUN mkdir -p /build/desktop-ui/src/main/kotlin
# Layer 5: Build release native binary
ENV GRADLE_OPTS="-Xmx2g"
RUN --mount=type=cache,target=/home/builder/.gradle/caches,id=fast-sim-gradle,uid=1001,gid=1001 \
--mount=type=cache,target=/home/builder/.gradle/wrapper,id=fast-sim-wrapper,uid=1001,gid=1001 \
--mount=type=cache,target=/home/builder/.m2/repository,id=fast-sim-m2,uid=1001,gid=1001 \
--mount=type=secret,id=github_actor,uid=1001,gid=1001,mode=0400 \
--mount=type=secret,id=github_token,uid=1001,gid=1001,mode=0400 \
GITHUB_ACTOR="$(cat /run/secrets/github_actor)" \
GITHUB_TOKEN="$(cat /run/secrets/github_token)" \
./gradlew :fast-sim:linkReleaseExecutableLinuxX64 --no-daemon --warning-mode=summary
# Verify binary was created
RUN ls -lh /build/fast-sim/build/bin/linuxX64/releaseExecutable/fast-sim.kexe
# ============================================
# Stage 2: Minimal runtime image
# ============================================
# Alpine Linux base (~8MB) instead of Ubuntu/Debian slim (~85MB+ with libxml2+libicu).
# Fixes #421: image exceeded 120MB target because Debian/Ubuntu's libxml2
# depends on libicu (~30MB). Alpine's libxml2 has no ICU dependency.
# The builder is pinned to Ubuntu 24.04 LTS (Noble) so its libxml2.so.2
# SONAME matches Alpine 3.21's libxml2 at runtime.
FROM --platform=linux/amd64 alpine:3.21
# gcompat: glibc ABI compatibility layer — K/N linuxX64 binaries link against glibc.
# Provides /lib64/ld-linux-x86-64.so.2 and glibc symbol wrappers.
# libgcc: GCC runtime support library — required alongside gcompat for complete
# glibc ABI compatibility on musl-based Alpine.
# libxml2: XML parsing via Kotlin/Native cinterop (linked with -lxml2 at build time).
# Alpine's libxml2 does NOT pull libicu, unlike Debian's (~30MB saved).
RUN apk add --no-cache \
gcompat \
libgcc \
libxml2
# Run the native binary as an unprivileged user for image hygiene.
# Use UID/GID 1001 to avoid colliding with the base image's ubuntu group (1000).
ARG RUNTIME_UID=1001
ARG RUNTIME_GID=1001
RUN addgroup -g ${RUNTIME_GID} app \
&& adduser -u ${RUNTIME_UID} -G app -s /bin/sh -D app \
&& mkdir -p /build/core/src/commonMain/resources \
&& chown -R app:app /build/core/src/commonMain/resources
# Copy native binary from builder
COPY --chown=app:app --from=builder /build/fast-sim/build/bin/linuxX64/releaseExecutable/fast-sim.kexe \
/usr/local/bin/fast-sim
# Native resource roots are baked into the binary as absolute build-tree paths. (This is intentional; resources are accessed via absolute paths baked at compile time; see Resources.read implementation.)
# Only commonMain/resources is required at runtime (vyhybna.xml, data.xsd via
# BuiltinNetworks and XmlSchemaContent). Test-only roots (core-test/commonMain
# and core/commonTest) are intentionally omitted to keep the release image small;
# Resources.read skips missing roots silently.
COPY --chown=app:app --from=builder /build/core/src/commonMain/resources /build/core/src/commonMain/resources
USER app
ENTRYPOINT ["fast-sim"]
CMD ["--help"]