# ================================ # Asset image # ================================ FROM node:22-alpine@sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32 AS assets ARG ESBUILD_VERSION=0.28.1 ARG OXIPNG_VERSION=9.1.5 ARG SVGO_VERSION=4.0.2 # Install the minifiers in their own layer, so they are cached across asset changes. # The oxipng pin is fuzzy (=~) so Alpine package revision bumps (-r0, -r1, ...) do not break the build when the base # image advances. RUN apk add --no-cache "oxipng=~${OXIPNG_VERSION}" \ && npm install --global "esbuild@${ESBUILD_VERSION}" "svgo@${SVGO_VERSION}" # Copy the static files and minify the JS/CSS/SVG sources and losslessly recompress the PNG images in place, keeping # their names so the URL paths derived from the StaticFile enumeration stay unchanged. WORKDIR /static COPY ./Services/Website/Resources/Static . RUN esbuild --minify --allow-overwrite --outdir=css css/*.css \ && esbuild --minify --allow-overwrite --outdir=js js/*.js \ && oxipng --opt max --strip safe --recursive . \ && svgo --recursive --folder . # Export stage: `docker build --target assets-export --output ` writes the minified static files to for # local inspection. FROM scratch AS assets-export COPY --from=assets /static / # ================================ # Build image # ================================ FROM swift:6.3-noble@sha256:69bf1f0281e13d82c9e49d67c2dd1dcc8c00bad738c860f4323d7078787ec8ea AS build # Install OS updates RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ && apt-get -q update \ && apt-get -q dist-upgrade -y \ && apt-get install -y libjemalloc-dev \ && rm -rf /var/lib/apt/lists/* # Set up a build area WORKDIR /build # First just resolve dependencies. # This creates a cached layer that can be reused as long as the manifests do not change. The Website package depends on # the local Localization package via a relative path, so its manifest must be present for resolution to succeed. COPY ./Packages/Localization/Package.swift ./Packages/Localization/ COPY ./Packages/Persistence/Package.swift ./Packages/Persistence/ COPY ./Packages/Infrastructure/Package.swift ./Packages/Infrastructure/ COPY ./Packages/Utility/Package.swift ./Packages/Utility/ COPY ./Services/Website/Package.swift ./Services/Website/Package.resolved ./Services/Website/ RUN swift package --package-path ./Services/Website resolve # Copy only the Swift inputs needed for a release build. Static assets are built in the assets stage and copied into # staging after the binary is produced. COPY ./Packages/Infrastructure/Sources ./Packages/Infrastructure/Sources COPY ./Packages/Localization/Sources ./Packages/Localization/Sources COPY ./Packages/Persistence/Sources ./Packages/Persistence/Sources COPY ./Packages/Utility/Sources ./Packages/Utility/Sources COPY ./Services/Website/Sources ./Services/Website/Sources # Build the application, with optimizations, with static linking, and using jemalloc. # # SPM validates the path of every target in the root package — including the test targets — even when only the # executable product is built, so those directories have to exist. They are created empty instead of copied: the test # sources are not needed to compile the product, keeping them out means editing a test never invalidates this layer, # and it keeps the local database bind mount under Tests/DB out of the image entirely. RUN mkdir -p ./Services/Website/Tests/App ./Services/Website/Tests/Library \ && swift build --package-path ./Services/Website -c release \ --product "Website" \ --static-swift-stdlib \ -Xlinker -ljemalloc # Switch to the staging area WORKDIR /staging # Copy main executable to staging area RUN cp "$(swift build --package-path /build/Services/Website -c release --show-bin-path)/Website" ./ # Copy static swift backtracer binary to staging area RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./ # Copy resources bundled by SPM to staging area RUN find -L "$(swift build --package-path /build/Services/Website -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \; # Create the static files directory (served by FileMiddleware) and fill it with the minified copies from the assets stage RUN mkdir -p ./Resources/Static COPY --from=assets /static ./Resources/Static # Ensure that by default, neither the directory nor any of its contents are writable. RUN chmod -R a-w ./Resources # ================================ # Run image # ================================ FROM ubuntu:noble@sha256:561618e2c15bf2397621dd04f96926663a3b5616c189cf7e38db7e82f5c538ea # Make sure all system packages are up to date, and install only essential packages. RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \ && apt-get -q update \ && apt-get -q dist-upgrade -y \ && apt-get -q install -y \ libjemalloc2 \ ca-certificates \ curl \ tzdata \ # If your app or its dependencies import FoundationNetworking, also install `libcurl4`. # libcurl4 \ # If your app or its dependencies import FoundationXML, also install `libxml2`. # libxml2 \ && rm -r /var/lib/apt/lists/* # Create a hummingbird user and group with /app as its home directory RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app hummingbird # Switch to the new home directory WORKDIR /app # Copy built executable and any staged resources from builder COPY --from=build --chown=hummingbird:hummingbird /staging /app # Provide configuration needed by the built-in crash reporter and some sensible default behaviors. ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static # Ensure all further commands run as the hummingbird user USER hummingbird:hummingbird # Let Docker bind to port 8080 EXPOSE 8080 # Start the Hummingbird service when the image is run, default to listening on 8080 in production environment ENTRYPOINT ["./Website"] CMD ["--http-host", "0.0.0.0", "--http-port", "8080"]