diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..fa489420 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.env + +/tmp/* +/log/*.log + +/vendor/bundle +.bundle + +.bash_history +.byebug_hist +.pry_history + +docker-compose.yml + +.semaphore-cache diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 6edad1a3..fd5d97ee 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -11,74 +11,68 @@ execution_time_limit: auto_cancel: queued: - when: 'true' + when: "true" fail_fast: stop: - when: 'true' + when: "true" global_job_config: - env_vars: - - name: RAILS_ENV - value: 'test' - - name: DATABASE_URL - value: 'postgresql://postgres@localhost/test?encoding=utf8' - - name: BUNDLE_ARGS - value: '--deployment --without development staging production --jobs 4' - prologue: commands: + # Setup dynamic environment variables b/c they do not support via env_vars yet + - export DOCKER_REPO="docker.pkg.github.com/fs/rails-base" + - export BUILDER_NAME="${DOCKER_REPO}/builder:${SEMAPHORE_GIT_BRANCH}" + - export IMAGE_NAME="${DOCKER_REPO}/final:${SEMAPHORE_GIT_BRANCH}" + - export RAILS_ENV="test" + - export RACK_ENV="test" + - export BUNDLE_WITHOUT="development staging production" + - export DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL="true" + - export MAILER_SENDER_ADDRESS="noreply@example.com" + + # Authenticate with DockerHub + - echo "${GITHUB_TOKEN}" | docker login https://docker.pkg.github.com -u "${GITHUB_USERNAME}" --password-stdin + - checkout - - cache restore - - cp .env.example .env blocks: - - name: Setup + - name: Build task: + secrets: + - name: semaphore-github-packages-secrets jobs: - - name: Bundle - commands: - - bundle install ${BUNDLE_ARGS} - - cache store + - name: Docker build + commands: + - docker pull "${BUILDER_NAME}" || true + - docker pull "${IMAGE_NAME}" || true + - docker build -t "${BUILDER_NAME}" --target Builder --cache-from="${BUILDER_NAME}" --build-arg BUNDLE_WITHOUT="${BUNDLE_WITHOUT}" --build-arg BUNDLER_VERSION=2.1.4 . + - docker build -t "${IMAGE_NAME}" --target Final --cache-from="${BUILDER_NAME}" --cache-from="${IMAGE_NAME}" --build-arg BUNDLE_WITHOUT="${BUNDLE_WITHOUT}" --build-arg BUNDLER_VERSION=2.1.4 . + - docker push "${BUILDER_NAME}" + - docker push "${IMAGE_NAME}" - - name: Quality + - name: Run task: - jobs: - - name: Quality - commands: - - bundle install ${BUNDLE_ARGS} --local - - bin/quality + secrets: + - name: github-docker-secrets - - name: Test - task: prologue: commands: - - nvm use - - sem-version ruby 2.5.7 - - bundle install ${BUNDLE_ARGS} --local - - sem-service start postgres - - bin/rails db:setup - - bin/rails assets:precompile + - docker pull "${IMAGE_NAME}" + - cp docker-compose.linux.yml docker-compose.override.yml + - docker-compose up --detach jobs: - - name: Unit + - name: Run RSpec commands: - - bin/rspec --tag ~type:feature + - docker-compose exec app bin/rails db:create db:schema:load + - bin/tests - - name: Features + - name: Run Quality commands: - - bin/rspec --tag type:feature - - # - name: Jasmine - # commands: - # - bundle exec rails jasmine:ci + - bin/quality promotions: - name: Deploy to Heroku pipeline_file: heroku.yml - - # Continuous deployment from master branch - auto_promote_on: - - result: passed - branch: - - master + auto_promote: + when: "result = 'passed' and branch = 'master'" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..6b370567 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,63 @@ +###################### +# Stage: Builder +FROM ruby:2.7.1-alpine as Builder + +ARG BUNDLER_VERSION + +RUN apk add --update --no-cache \ + build-base \ + postgresql-dev \ + git \ + nodejs \ + npm \ + imagemagick \ + tzdata + +# Remove bundle config if exist +RUN rm -f .bundle/config + +RUN gem install bundler:$BUNDLER_VERSION + +WORKDIR /app + +# Install gems +ARG BUNDLE_WITHOUT +ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT} + +RUN bundle config set without ${BUNDLE_WITHOUT} + +COPY Gemfile* /app/ +RUN bundle install -j4 --retry 3 \ + # Remove unneeded files (cached *.gem, *.o, *.c) + && rm -rf /usr/local/bundle/cache/*.gem \ + && find /usr/local/bundle/gems/ -name "*.c" -delete \ + && find /usr/local/bundle/gems/ -name "*.o" -delete + +RUN npm install -g yarn && yarn install + +# Add the Rails app +COPY . /app/ + +# Remove folders not needed in resulting image +ARG FOLDERS_TO_REMOVE +RUN rm -rf $FOLDERS_TO_REMOVE + +############################### +# Stage Final +FROM ruby:2.7.1-alpine as Final + +# Add Alpine packages +RUN apk add --update --no-cache \ + postgresql-client \ + imagemagick \ + tzdata \ + file \ + git \ + nodejs \ + npm + +# Copy app with gems from former build stage +COPY --from=Builder /usr/local/bundle/ /usr/local/bundle/ +COPY --from=Builder /app/ /app/ + +WORKDIR /app diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 00000000..ef132805 --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e + +if [ -f tmp/pids/server.pid ]; then + rm tmp/pids/server.pid +fi + +bundle exec rails server -b 0.0.0.0 diff --git a/bin/docker-sync b/bin/docker-sync new file mode 100755 index 00000000..192ce520 --- /dev/null +++ b/bin/docker-sync @@ -0,0 +1,7 @@ +#!/usr/bin/env sh + +set -e + +gem install docker-sync +docker-sync start +cp docker-compose.osx.yml docker-compose.override.yml diff --git a/docker-compose.linux.yml b/docker-compose.linux.yml new file mode 100644 index 00000000..05da2d62 --- /dev/null +++ b/docker-compose.linux.yml @@ -0,0 +1,6 @@ +version: "3.4" + +services: + app: + volumes: + - .:/app diff --git a/docker-compose.osx.yml b/docker-compose.osx.yml new file mode 100644 index 00000000..a66f3b55 --- /dev/null +++ b/docker-compose.osx.yml @@ -0,0 +1,10 @@ +version: "3.4" + +services: + app: + volumes: + - app-files:/app:nocopy + +volumes: + app-files: + external: true diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..00a31bd9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: "3.4" + +x-app: &app_base + depends_on: + - db + image: ${IMAGE_NAME} + environment: + - DATABASE_URL=postgres://postgres:password@db + - RAILS_ENV + - RACK_ENV + - DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL + - AUTH_SECRET_TOKEN + - MAILER_SENDER_ADDRESS + - PASSWORD_RECOVERY_LINK_TEMPLATE + build: + context: . + args: + - BUNDLE_WITHOUT="${BUNDLE_WITHOUT}" + - BUNDLER_VERSION=2.1.4 + - FOLDERS_TO_REMOVE="" + links: + - db + volumes: + - ruby-bundle:/usr/local/bundle + +services: + db: + image: postgres:12-alpine + environment: + - POSTGRES_PASSWORD=password + ports: + - "5432:5432" + volumes: + - db-data:/var/lib/postgresql/data + mailcatcher: + image: yappabe/mailcatcher + ports: + - "1025:1025" + - "1080:1080" + app: + <<: *app_base + depends_on: + - mailcatcher + ports: + - "3000:3000" + command: bin/docker-entrypoint + +volumes: + ruby-bundle: + db-data: diff --git a/docker-sync.yml b/docker-sync.yml new file mode 100644 index 00000000..31baf7fa --- /dev/null +++ b/docker-sync.yml @@ -0,0 +1,6 @@ +version: "2" +syncs: + app-files: + notify_terminal: true + src: './' + sync_excludes: ['.git', '.bundle']