Why does my docker image keeps getting rebuilt form scratch? (kamal, docker buildx)

89 views Asked by At

When I do kamal deploy my image keeps being re-created from scratch (e.g. it's compiling ruby each time).

Here is my 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"]

And my 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: "[email protected]"
    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

Logs: https://gist.github.com/dorianmariecom/e51aba0648efcfcef66b88cd01fda24b

1

There are 1 answers

0
Dorian On

The solution was the build remotely on the server and from GitHub Actions:

config/deploy.yml

service: code
image: dorianmariefr/code
builder:
  remote:
    arch: amd64
    ssh: [email protected]
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:
    - KAMAL_REGISTRY_USERNAME
  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: "[email protected]"
    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

.github/workflows/deploy.yml

name: Deploy
concurrency:
  group: production
  cancel-in-progress: true
on:
  push:
    branches:
      - main
jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      DOCKER_BUILDKIT: 1
      RAILS_ENV: production
      RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
      KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }}
      KAMAL_REGISTRY_USERNAME: ${{ secrets.KAMAL_REGISTRY_USERNAME }}
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.3.0
          bundler-cache: true
      - run: gem install kamal
      - uses: webfactory/[email protected]
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
      - uses: docker/setup-buildx-action@v3
      - run: kamal lock release
      - run: kamal redeploy