diff --git a/.circleci/config.yml b/.circleci/config.yml index 4bc645c65..54e2b4625 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,8 @@ jobs: steps: - checkout # - browser-tools/install-firefox - - ruby/install-deps + - ruby/install-deps: + key: gems-v2- - run: command: 'dockerize -wait tcp://localhost:5432 -timeout 1m' name: Wait for DB diff --git a/Dockerfile b/Dockerfile index 46424da1c..9d6f83f91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,30 @@ -# syntax=docker/dockerfile:1 -FROM ruby:3.1 -RUN apt-get update -qq && apt-get install -y nodejs postgresql-client +FROM ruby:3.1-slim-bullseye as base +RUN gem install bundler \ + && apt-get update \ + && apt-get upgrade --yes \ + && rm -rf /var/lib/apt/lists/* /var/lib/apt/archives/*.deb +ENV TZ='Europe/London' +ENV RUBYOPT='-W:no-deprecated -W:no-experimental' + +# Here we build all our ruby gems, node modules etc, for copying into our slimmer image. +FROM base AS builder WORKDIR /app -COPY Gemfile /app/Gemfile -RUN bundle install && cp Gemfile.lock /tmp +RUN apt-get update \ + && apt-get install --yes --no-install-recommends \ + build-essential libpq-dev libxml2-dev libxslt1-dev git \ + && rm -rf /var/lib/apt/lists/* /var/lib/apt/archives/*.deb +COPY Gemfile Gemfile.lock /app/ +RUN bundle install --jobs 4 \ + && bundle binstubs --all --path /usr/local/bundle/bin -# Add a script to be executed every time the container starts. -COPY entrypoint.sh /usr/bin/ -RUN chmod +x /usr/bin/entrypoint.sh -ENTRYPOINT ["entrypoint.sh"] +# Slim application image without development dependencies +FROM base AS app +WORKDIR /app +COPY . /app +COPY --from=builder /usr/local/bundle /usr/local/bundle +COPY --from=builder /node_modules /node_modules +COPY --from=builder Gemfile Gemfile.lock package.json yarn.lock .yarnrc /app/ +CMD ["rails", "server", "-b", "0.0.0.0"] EXPOSE 3009 -# Configure the main process to run when running the image -CMD ["rails", "server", "-p", "3009", "-b", "0.0.0.0"] +# TODO: Sort out a production container with compiled assets etc. diff --git a/Gemfile.lock b/Gemfile.lock index b3391192e..e4b36060c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,6 +145,8 @@ GEM net-smtp (0.3.3) net-protocol nio4r (2.5.8) + nokogiri (1.13.9-aarch64-linux) + racc (~> 1.4) nokogiri (1.13.9-x86_64-linux) racc (~> 1.4) parallel (1.22.1) @@ -280,6 +282,7 @@ GEM zeitwerk (2.6.6) PLATFORMS + aarch64-linux x86_64-linux DEPENDENCIES diff --git a/README.md b/README.md index 55d7758f4..b4bfc5dcf 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,14 @@ Start the application and its dependencies via docker: docker-compose up ``` +## Updating gems inside the container + +This can be done with the `bin/with_builder.sh` script: +``` +./bin/with_builder.sh bundle update +``` +which should update the Gems in the container, without the need for rebuilding. + # CORS Allowed Origins Add a comma separated list to the relevant enviroment settings. E.g for development in the `.env` file: diff --git a/bin/with-builder.sh b/bin/with-builder.sh new file mode 100755 index 000000000..06e69b4a3 --- /dev/null +++ b/bin/with-builder.sh @@ -0,0 +1,51 @@ +#!/bin/bash -eu + +# This script takes a command and executes it inside a container created with +# the editor-api:builder image, allowing easy-ish updates to Gemfile.lock and +# yarn.lock. + +cmd="$*" + +# The image we're working against +image="editor-api:builder" + +# Files we copy in to the builder, and then out again. These files are the +# ones in the builder target of the Dockerfile. +files="Gemfile Gemfile.lock" + +# Change to the repo root directory. +cd $(dirname $0)/.. + +function log() { + echo '>>> ' $* +} + +# Container inside which we'll make our changes +log "Creating new container from $image" +builder=$(docker create -it $image $cmd) + +function cleanup () { + log "Removing $image container" + docker rm -f $builder || true +} + +# Ensure our created container is removed on exit +trap cleanup EXIT + +log "Copying files to $image container" +for f in $files ; do + docker cp $f $builder:/app/ +done + +log "Starting $image container" +docker start -ai $builder + +# Now the command has run, commit the changes, so that if we run this again, +# we'll start from this point. +log "Committing changes against the $image image" +docker commit $builder $image + +log "Copying files back out of the $image container" +for f in $files ; do + docker cp $builder:/app/$f $f +done diff --git a/docker-compose.yml b/docker-compose.yml index 8ec82b66c..145d883b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,20 +1,37 @@ -version: "3.9" +version: "3.8" + services: db: - image: postgres:14.1 + image: postgres:14 environment: - - POSTGRES_USER - - POSTGRES_PASSWORD - POSTGRES_DB + - POSTGRES_PASSWORD + - POSTGRES_USER - web: - build: . - command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3009 -b '0.0.0.0'" + api: + build: + context: . + target: builder + image: editor-api:builder + depends_on: + - db volumes: - .:/app + # This is here to avoid rails finding stale pid-files in tmp/pids and then + # thinking it is already running: + - type: tmpfs + target: /app/tmp/pids + # The cache should be on tmpfs too, to ensure it gets wiped between runs + - type: tmpfs + target: /app/tmp/cache + command: bin/rails server --port 3009 --binding 0.0.0.0 + # NB: The API runs on port 3009. ports: - "3009:3009" - depends_on: - - db - stdin_open: true - tty: true + stdin_open: true # For docker run --interactive, i.e. keep STDIN open even if not attached + tty: true # For docker run --tty, i.e. allocate a pseudo-TTY. Important to allow interactive byebug sessions + environment: + - POSTGRES_HOST=db + - POSTGRES_DB + - POSTGRES_PASSWORD + - POSTGRES_USER diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index 03d1496b3..000000000 --- a/entrypoint.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -e - -built_lock_file="/tmp/Gemfile.lock" -current_lock_file="Gemfile.lock" - -# Copy built lock file into container/host fs -function cp_built_lock_file() { - cp "${built_lock_file}" "${current_lock_file}" -} - -if [ -f "${current_lock_file}" ]; then - diff="$(diff "${built_lock_file}" "${current_lock_file}")" - if [ "${diff}" != "" 2>/dev/null ]; then - cp_built_lock_file - fi -else - cp_built_lock_file -fi - -# Remove a potentially pre-existing server.pid for Rails. -rm -f /myapp/tmp/pids/server.pid - -# Then exec the container's main process (what's set as CMD in the Dockerfile). -exec "$@"