执行kamal deploy时Docker镜像为何总是从头重建?
解决Kamal部署时Docker镜像重复从头构建的问题
问题现象
执行kamal deploy命令时,Docker镜像每次都会从头开始构建,包括重复编译Ruby、安装依赖等耗时步骤,严重拖慢部署效率。
相关配置
Dockerfile
FROM debian:bullseye-slim as base ENV BUNDLER_VERSION="2.5.5" \ BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ BUNDLE_WITHOUT="development" \ NODE_VERSION="20.11.0" \ NPM_VERSION="10.4.0" \ RAILS_ENV="production" \ RUBY_INSTALL_VERSION="0.9.3" \ RUBY_VERSION="3.3.0" \ YARN_VERSION="1.22.19" ENV PATH="/opt/rubies/ruby-${RUBY_VERSION}/bin:/usr/local/node/bin:${PATH}" RUN apt-get update && \ apt-get install -y \ autoconf \ build-essential \ curl \ fish \ git \ libpq-dev \ libvips \ pandoc \ pkg-config \ postgresql-client \ vim \ wget RUN wget "https://github.com/postmodern/ruby-install/releases/download/v${RUBY_INSTALL_VERSION}/ruby-install-${RUBY_INSTALL_VERSION}.tar.gz" \ && tar -xzvf "ruby-install-${RUBY_INSTALL_VERSION}.tar.gz" \ && cd "ruby-install-${RUBY_INSTALL_VERSION}" \ && make install RUN ruby-install -p https://github.com/ruby/ruby/pull/9371.diff ruby "${RUBY_VERSION}" WORKDIR /rails FROM base as build RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ npm install -g "yarn@${YARN_VERSION}" && \ rm -rf /tmp/node-build-master COPY Gemfile Gemfile.lock ./ RUN gem install bundler -v "${BUNDLER_VERSION}" RUN bundle install && \ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ bundle exec bootsnap precompile --gemfile COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile COPY . . RUN bundle exec bootsnap precompile app/ lib/ RUN HOST=example.com \ BASE_URL=https://example.com \ RAILS_MASTER_KEY_DUMMY=1 \ SECRET_KEY_BASE_DUMMY=1 \ ./bin/rails assets:precompile FROM base COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=build /rails /rails RUN useradd rails --create-home --shell /bin/bash && \ chown -R rails:rails db log storage tmp USER rails:rails ENTRYPOINT ["/rails/bin/docker-entrypoint"] EXPOSE 3000 CMD ["./bin/rails", "server"]
config/deploy.yml
service: code image: dorianmariefr/code servers: web: hosts: - 165.232.149.13 labels: traefik.http.routers.code.rule: Host(`code.dorianmarie.com`) traefik.http.routers.code_secure.entrypoints: websecure traefik.http.routers.code_secure.rule: Host(`code.dorianmarie.com`) traefik.http.routers.code_secure.tls.certresolver: letsencrypt traefik.http.routers.code_secure.tls: true registry: username: dorianmariefr password: - KAMAL_REGISTRY_PASSWORD env: clear: HOST: code.dorianmarie.com BASE_URL: https://code.dorianmarie.com secret: - RAILS_MASTER_KEY - POSTGRES_PASSWORD traefik: options: publish: - "443:443" volume: - "/letsencrypt/acme.json:/letsencrypt/acme.json" args: entryPoints.web.address: ":80" entryPoints.websecure.address: ":443" entryPoints.web.http.redirections.entryPoint.to: websecure entryPoints.web.http.redirections.entryPoint.scheme: https entryPoints.web.http.redirections.entrypoint.permanent: true certificatesResolvers.letsencrypt.acme.email: "dorian@dorianmarie.fr" certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json" certificatesResolvers.letsencrypt.acme.httpchallenge: true certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web accessories: db: image: postgres:16.1 host: 165.232.149.13 port: 5432 env: clear: POSTGRES_USER: code POSTGRES_DB: code_production secret: - POSTGRES_PASSWORD directories: - data:/var/lib/postgresql/data
解决方案
1. 启用Kamal构建缓存
在config/deploy.yml中添加build配置,开启远程缓存功能,让Kamal复用之前构建的镜像层:
build: cache: true
该配置会让Kamal在构建时调用Docker的构建缓存机制,只要基础层(如Ruby安装、系统依赖)未发生变化,就不会重新执行编译步骤。
2. 优化Dockerfile分层顺序
确保Dockerfile中不变的步骤放在最前面,Docker会自动缓存这些层,仅当后续可变步骤(如代码复制、资产预编译)发生变化时才重新执行:
- 保留
base阶段的系统依赖安装、Ruby安装步骤在最前端,只要版本号不变,这些层就会被永久缓存。 build阶段先复制Gemfile/Gemfile.lock、package.json/yarn.lock,再执行依赖安装,仅当依赖文件更新时才会重新安装依赖。
3. 预构建Ruby基础镜像(可选)
如果Ruby版本和补丁长期稳定,可以单独构建包含Ruby的基础镜像并推送到镜像仓库,之后在Dockerfile中直接引用该镜像,彻底避免每次部署都编译Ruby:
# 替换原base阶段,使用预构建的Ruby基础镜像 FROM your-registry/ruby-3.3.0-bullseye:latest as base # 后续构建步骤保持不变
验证
修改配置后重新执行kamal deploy,观察构建日志,若出现Using cache的提示,说明已成功复用镜像层,不会再从头编译Ruby。
内容的提问来源于stack exchange,提问作者Dorian
相关产品推荐
相关产品推荐

