# Ruby version for both stages. Keep in sync with `.mise.toml` and the Gemfile's # `ruby '4.0.7'` directive. The image ships Bundler 4.0.20, which the lockfile's # BUNDLED WITH now pins, so no separate bundler install is needed. ARG RUBY_VERSION=4.0.7 FROM ruby:${RUBY_VERSION}-alpine AS builder ARG BUILD_PACKAGES="build-base libxml2 libxslt git" ARG DEV_PACKAGES="ruby-dev libffi-dev libxml2-dev libxslt-dev yaml-dev postgresql-dev nodejs npm pnpm zlib-dev imagemagick-dev libwebp-dev libpng-dev tiff-dev gcompat" ARG RUBY_PACKAGES="tzdata" ARG REPLACE_CHINA_MIRROR="true" ARG ORIGINAL_REPO_URL="dl-cdn.alpinelinux.org" ARG MIRROR_REPO_URL="mirrors.ustc.edu.cn" ARG RUBYGEMS_SOURCE="https://gems.ruby-china.com" ARG NPM_REGISTRY="https://registry.npmmirror.com" ARG APP_ROOT="/app" ENV BUNDLE_APP_CONFIG="$APP_ROOT/.bundle" \ RAILS_ENV="production" \ RUBY_YJIT_ENABLE="true" # System dependencies RUN set -ex && \ if [[ "$REPLACE_CHINA_MIRROR" == "true" ]]; then \ sed -i "s/$ORIGINAL_REPO_URL/$MIRROR_REPO_URL/g" /etc/apk/repositories && \ gem sources --add $RUBYGEMS_SOURCE --remove https://rubygems.org/ && \ bundle config mirror.https://rubygems.org $RUBYGEMS_SOURCE; \ fi && \ apk --update --no-cache add $BUILD_PACKAGES $DEV_PACKAGES $RUBY_PACKAGES && \ if [[ "$REPLACE_CHINA_MIRROR" == "true" ]]; then \ pnpm config set registry $NPM_REGISTRY; \ fi WORKDIR $APP_ROOT # Node dependencies COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN pnpm install --frozen-lockfile # Ruby dependencies COPY Gemfile Gemfile.lock ./ RUN bundle config set --global frozen 1 && \ bundle config set deployment 'true' && \ bundle config set without 'development test' && \ bundle config set path 'vendor/bundle' && \ bundle install --jobs $(nproc) --retry 3 # Platform assertion: no non-musl gem artifact may ship, enforced by the # script docker/platform_assertion.rb. It replaces force_ruby_platform, added # 2022-12 (commit 5b495933) because nokogiri shipped no precompiled musl gem # then. That flag disabled platform resolution instead of verifying its # result, so glibc artifacts could be installed unnoticed. Removal is safe # now: ffi 1.17.4, google-protobuf 4.33.0, nokogiri 1.19.4 and pg 1.7.0 # publish musl-native platforms, now locked in Gemfile.lock. If this check # fails, fix the offending gem's resolution instead of restoring the flag. COPY docker/platform_assertion.rb /tmp/platform_assertion.rb RUN bundle exec ruby /tmp/platform_assertion.rb COPY . $APP_ROOT RUN SECRET_KEY_BASE=precompile_placeholder bin/rails vite:build # Remove folders not needed in resulting image. The gem directory is resolved # from the interpreter instead of hard-coding the ABI path (ruby/3.4.0), so a # Ruby bump cannot silently skip this cleanup. RUN rm -rf docker node_modules tmp/cache spec .browserslistrc babel.config.js \ package.json postcss.config.js pnpm-lock.yaml && \ cd "/app/vendor/bundle/ruby/$(ruby -e 'print RbConfig::CONFIG["ruby_version"]')" && \ rm -rf cache/*.gem && \ find gems/ -name "*.c" -delete && \ find gems/ -name "*.o" -delete ################################################################################## ARG RUBY_VERSION FROM ruby:${RUBY_VERSION}-alpine ARG REPLACE_CHINA_MIRROR="true" ARG ORIGINAL_REPO_URL="dl-cdn.alpinelinux.org" ARG MIRROR_REPO_URL="mirrors.ustc.edu.cn" ARG RUBYGEMS_SOURCE="https://gems.ruby-china.com/" ARG PACKAGES="tzdata curl logrotate postgresql-client postgresql-dev imagemagick imagemagick-dev libwebp-dev libpng-dev tiff-dev openssl openssl-dev caddy gcompat xz" ARG APP_ROOT=/app ARG S6_OVERLAY_VERSION="3.2.3.2" ARG TARGETARCH ENV TZ="Asia/Shanghai" \ PS1="$(whoami)@$(hostname):$(pwd)$ " \ BUNDLE_APP_CONFIG="$APP_ROOT/.bundle" \ RAILS_ENV="production" \ RUBY_YJIT_ENABLE="true" \ S6_BEHAVIOUR_IF_STAGE2_FAILS="2" \ S6_SERVICES_GRACETIME="15000" \ S6_KEEP_ENV="1" # System dependencies RUN set -ex && \ if [[ "$REPLACE_CHINA_MIRROR" == "true" ]]; then \ sed -i "s/$ORIGINAL_REPO_URL/$MIRROR_REPO_URL/g" /etc/apk/repositories && \ gem sources --add $RUBYGEMS_SOURCE --remove https://rubygems.org/; \ fi && \ apk --update --no-cache add $PACKAGES && \ echo "Setting variables for ${TARGETARCH}" && \ case "$TARGETARCH" in \ "amd64") \ S6_OVERLAY_ARCH="x86_64" \ ;; \ "arm64") \ S6_OVERLAY_ARCH="aarch64" \ ;; \ *) \ echo "Doesn't support $TARGETARCH architecture" \ exit 1 \ ;; \ esac && \ S6_BASE_URL="https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}" && \ curl -L -s "${S6_BASE_URL}/s6-overlay-noarch.tar.xz" | tar -Jxvf - -C / && \ curl -L -s "${S6_BASE_URL}/s6-overlay-${S6_OVERLAY_ARCH}.tar.xz" | tar -Jxvf - -C / && \ curl -L -s "${S6_BASE_URL}/s6-overlay-symlinks-noarch.tar.xz" | tar -Jxvf - -C / && \ curl -L -s "${S6_BASE_URL}/s6-overlay-symlinks-arch.tar.xz" | tar -Jxvf - -C / WORKDIR $APP_ROOT COPY docker/rootfs / COPY --from=builder $APP_ROOT $APP_ROOT # Native-extension smoke: prove the delivered runtime stage's shared libraries # are complete. It must run here, not in the builder -- the two stages install # different system packages, and only this stage reflects the shipped image. # The requires and the pass line must stay in sync. COPY docker/native_extension_smoke.rb /tmp/native_extension_smoke.rb RUN bundle exec ruby /tmp/native_extension_smoke.rb && rm /tmp/native_extension_smoke.rb RUN ln -s /app/bin/rails /usr/local/bin/ ARG BUILD_DATE ARG VCS_REF ARG TAG ARG ZEALOT_VERSION ENV DOCKER_TAG="$TAG" \ ZEALOT_VCS_REF="$VCS_REF" \ ZEALOT_BUILD_DATE="$BUILD_DATE" EXPOSE 80 # VOLUME [ "/app/public/uploads", "/app/public/backup" ] ENTRYPOINT ["/init"]