diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8d447c4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,57 @@ +# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. + +# Ignore git directory. +/.git/ +/.gitignore + +# Ignore bundler config. +/.bundle + +# Ignore all environment files. +/.env* + +# Ignore all default key files. +/config/master.key +/config/credentials/*.key + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/.keep + +# Ignore storage (uploaded files in development and any SQLite databases). +/storage/* +!/storage/.keep +/tmp/storage/* +!/tmp/storage/.keep + +# Ignore assets. +/node_modules/ +/app/assets/builds/* +!/app/assets/builds/.keep +/public/assets + +# Ignore CI service files. +/.github + +# Ignore Kamal files. +/config/deploy*.yml +/.kamal + +# Ignore development files +/.devcontainer + +# Ignore Docker-related files +/.dockerignore +/Dockerfile* + +# Vite Ruby +/public/vite* +# Vite uses dotenv and suggests to ignore local-only env files. See +# https://vitejs.dev/guide/env-and-mode.html#env-files +*.local diff --git a/.github/failed-vuln-check.md b/.github/failed-vuln-check.md new file mode 100644 index 0000000..65ac1d3 --- /dev/null +++ b/.github/failed-vuln-check.md @@ -0,0 +1,12 @@ +--- +title: Container Vulnerability Scanner Failed +labels: dls-work-cycle, security +--- +Trivy Vulnerability Scan failed. + +URL: {{ env.WORKFLOW_URL }} +Output: + +``` +{{ env.SCANNER_OUTPUTS }} +``` diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml new file mode 100644 index 0000000..fb0dc1a --- /dev/null +++ b/.github/workflows/build-docker-image.yml @@ -0,0 +1,150 @@ +name: Create and publish a Docker image +permissions: + issues: write + pull-requests: write + discussions: write + +on: + push: + branches: + - main + pull_request: + branches: + - main + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha + env: + DOCKER_METADATA_PR_HEAD_SHA: true + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + file: Dockerfile + container-vuln-scan: + needs: build-and-push-image + runs-on: ubuntu-latest + if: + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha + env: + DOCKER_METADATA_PR_HEAD_SHA: true + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.20.0 + id: runscanner + continue-on-error: true + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + with: + image-ref: 'ghcr.io/pulibrary/abid:${{ steps.meta.outputs.version }}' + format: 'table' + exit-code: '1' + ignore-unfixed: true + vuln-type: 'os' + severity: 'CRITICAL,HIGH' + output: 'vulnerabilities.table' + - name: Set variables + id: scanner + if: ${{ always() }} + run: | + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "results<<$EOF" >> $GITHUB_OUTPUT + echo "$(cat vulnerabilities.table)" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + - name: Output variable + if: ${{ always() }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: echo "${{ steps.scanner.outputs.results }}" + - name: Find Comment for scan + if: github.event_name == 'pull_request' + uses: peter-evans/find-comment@v3 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: 'Container Scanning Status: ' + - name: Create or update comment + if: github.event_name == 'pull_request' + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Container Scanning Status: ${{ steps.runscanner.outcome != 'success' && '❌ Failure' || '✅ Success' }} + ``` + ${{ steps.scanner.outputs.results }} + ``` + edit-mode: replace + - name: Create issue + if: steps.runscanner.outcome != 'success' && github.event_name != 'pull_request' + uses: JasonEtco/create-an-issue@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + SCANNER_OUTPUTS: ${{ steps.scanner.outputs.results }} + with: + filename: .github/failed-vuln-check.md + update_existing: true + - name: Find existing security issue + id: issues + if: steps.runscanner.outcome == 'success' && github.event_name != 'pull_request' + uses: lee-dohm/select-matching-issues@v1 + with: + query: 'Container Vulnerability Scanner Failed is:open ' + token: ${{ secrets.GITHUB_TOKEN }} + - name: Close found issues + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: steps.runscanner.outcome == 'success' && github.event_name != 'pull_request' + run: cat ${{ steps.issues.outputs.path }} | xargs gh issue close -c 'Container Scan Passing on Merge to Main' diff --git a/.github/workflows/nightly-vuln-scanning.yml b/.github/workflows/nightly-vuln-scanning.yml new file mode 100644 index 0000000..4b2b680 --- /dev/null +++ b/.github/workflows/nightly-vuln-scanning.yml @@ -0,0 +1,73 @@ +name: Run nightly vulnerability check +permissions: + issues: write + pull-requests: write + discussions: write + +on: + schedule: + - cron: '0 0 * * *' + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + container-vuln-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@0.20.0 + id: runscanner + continue-on-error: true + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 + with: + image-ref: 'ghcr.io/pulibrary/imagecat-rails:main' + format: 'table' + exit-code: '1' + ignore-unfixed: true + vuln-type: 'os' + severity: 'CRITICAL,HIGH' + output: 'vulnerabilities.table' + - name: Set variables + id: scanner + if: job.steps.runscanner.status == failure() + run: | + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + echo "results<<$EOF" >> $GITHUB_OUTPUT + echo "$(cat vulnerabilities.table)" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + - name: Output variable + if: job.steps.runscanner.status == failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: echo "${{ steps.scanner.outputs.results }}" + - name: Create issue + if: steps.runscanner.outcome != 'success' + uses: JasonEtco/create-an-issue@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + SCANNER_OUTPUTS: ${{ steps.scanner.outputs.results }} + with: + filename: .github/failed-vuln-check.md + update_existing: true + - name: Find existing security issue + id: issues + if: steps.runscanner.outcome == 'success' + uses: lee-dohm/select-matching-issues@v1 + with: + query: 'Container Vulnerability Scanner Failed is:open ' + token: ${{ secrets.GITHUB_TOKEN }} + - name: Close found issues + continue-on-error: true + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: steps.runscanner.outcome == 'success' + run: cat ${{ steps.issues.outputs.path }} | xargs gh issue close -c 'Container Scan Passing on Merge to Main' diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..ec7ba0e --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.10.0 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2c23091 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,141 @@ +# syntax=docker/dockerfile:1 +# check=error=true + +# Make sure RUBY_VERSION matches the Ruby version in .ruby-version +ARG RUBY_VERSION=3.2.6 +FROM ruby:$RUBY_VERSION-slim AS base + +# Rails app lives here +WORKDIR /rails + +# Update gems and bundler +RUN gem update --system --no-document && \ + gem install -N bundler + +# Install base packages +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y curl libjemalloc2 postgresql-client sqlite3 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Set production environment +ENV BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development:test" \ + RAILS_ENV="production" + + +# Throw-away build stages to reduce size of final image +FROM base AS prebuild + +# Install packages needed to build gems and node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential libffi-dev libpq-dev libyaml-dev node-gyp pkg-config python-is-python3 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + + +FROM prebuild AS node + +# Install JavaScript dependencies +ARG NODE_VERSION=22.10.0 +ARG YARN_VERSION=1.22.22 +ENV PATH=/usr/local/node/bin:$PATH +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 + +# Install node modules +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + + +FROM prebuild AS build + +# Install application gems +COPY Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile + +# Copy node modules +COPY --from=node /rails/node_modules /rails/node_modules +COPY --from=node /usr/local/node /usr/local/node +ENV PATH=/usr/local/node/bin:$PATH + +# Copy application code +COPY . . + +# Precompile bootsnap code for faster boot times +RUN bundle exec bootsnap precompile app/ lib/ + +# Precompiling assets for production without requiring secret RAILS_MASTER_KEY +RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile + + +# Final stage for app image +FROM base + +# Install packages needed for deployment +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y nginx ruby-foreman && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# configure nginx +RUN gem install foreman && \ + sed -i 's|pid /run|pid /rails/tmp/pids|' /etc/nginx/nginx.conf && \ + sed -i 's/access_log\s.*;/access_log \/dev\/stdout;/' /etc/nginx/nginx.conf && \ + sed -i 's/error_log\s.*;/error_log \/dev\/stderr info;/' /etc/nginx/nginx.conf + +COPY <<-"EOF" /etc/nginx/sites-available/default +server { + listen 3000 default_server; + listen [::]:3000 default_server; + access_log /dev/stdout; + + root /rails/public; + + location /cable { + proxy_pass http://localhost:8082/cable; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + proxy_set_header Host $host; + } + + location / { + try_files $uri @backend; + } + + location @backend { + proxy_pass http://localhost:3001; + proxy_set_header Host $http_host; + } +} +EOF + +# Copy built artifacts: gems, application +COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" +COPY --from=build /rails /rails + +# Run and own only the runtime files as a non-root user for security +RUN groupadd --system --gid 1000 rails && \ + useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \ + chown 1000:1000 /var/lib/nginx /var/log/nginx/* && \ + chown -R 1000:1000 db log storage tmp +USER 1000:1000 + +# Deployment options +ENV PORT="3001" + +# Entrypoint prepares the database. +ENTRYPOINT ["/rails/bin/docker-entrypoint"] + +# Build a Procfile for production use +COPY <<-"EOF" /rails/Procfile.prod +nginx: /usr/sbin/nginx -g "daemon off;" +rails: ./bin/rails server -p 3001 +EOF + +# Start the server by default, this can be overwritten at runtime +EXPOSE 3000 +CMD ["foreman", "start", "--procfile=Procfile.prod"] diff --git a/Gemfile b/Gemfile index a6e5949..38c87a3 100644 --- a/Gemfile +++ b/Gemfile @@ -3,8 +3,6 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby "3.2.6" - gem "alma" gem "archivesspace-client" gem "base64", "0.1.1" # To make the application bundle correctly @@ -21,7 +19,6 @@ gem "net-smtp", require: false gem "net-ssh", "7.0.0.beta1" gem "omniauth-cas" gem "pg" -gem "psych", "< 4" gem "puma", "~> 5.6" gem "rails", "~> 7.1.2" gem "rake" @@ -50,6 +47,7 @@ group :development do gem "capistrano-passenger", require: false gem "capistrano-rails", "~> 1.4", require: false gem "capistrano-yarn", require: false + gem "dockerfile-rails", ">= 1.7" gem "ed25519" gem "listen", "~> 3.3" gem "rack-mini-profiler", "~> 2.0" diff --git a/Gemfile.lock b/Gemfile.lock index e149dd7..57a0bc1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -148,6 +148,8 @@ GEM warden (~> 1.2.3) diff-lcs (1.5.0) docile (1.4.0) + dockerfile-rails (1.7.2) + rails (>= 3.0.0) dotenv (2.8.1) dotenv-rails (2.8.1) dotenv (= 2.8.1) @@ -224,6 +226,8 @@ GEM net-protocol net-ssh (7.0.0.beta1) nio4r (2.7.3) + nokogiri (1.16.5-aarch64-linux) + racc (~> 1.4) nokogiri (1.16.5-arm64-darwin) racc (~> 1.4) nokogiri (1.16.5-x86_64-darwin) @@ -251,7 +255,6 @@ GEM pry (>= 0.13, < 0.15) pry-rails (0.3.9) pry (>= 0.10.4) - psych (3.3.4) public_suffix (5.0.3) puma (5.6.8) nio4r (~> 2.0) @@ -398,6 +401,7 @@ GEM actionpack (>= 5.2) activesupport (>= 5.2) sprockets (>= 3.0.0) + sqlite3 (1.6.8-aarch64-linux) sqlite3 (1.6.8-arm64-darwin) sqlite3 (1.6.8-x86_64-darwin) sqlite3 (1.6.8-x86_64-linux) @@ -448,6 +452,7 @@ GEM zeitwerk (2.6.13) PLATFORMS + aarch64-linux arm64-darwin-21 arm64-darwin-22 arm64-darwin-23 @@ -470,6 +475,7 @@ DEPENDENCIES capybara (~> 3.37) cocoon devise + dockerfile-rails (>= 1.7) dotenv-rails ed25519 factory_bot_rails @@ -486,7 +492,6 @@ DEPENDENCIES pg pry-byebug pry-rails - psych (< 4) puma (~> 5.6) rack-mini-profiler (~> 2.0) rails (~> 7.1.2) @@ -506,8 +511,5 @@ DEPENDENCIES web-console (>= 4.1.0) webmock -RUBY VERSION - ruby 3.2.6p234 - BUNDLED WITH 2.3.26 diff --git a/README.md b/README.md index 16c85de..637a722 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,17 @@ To stop database services: `rake servers:stop` or `lando stop` - Ensure you're on VPN or the part of the login process where you connect to aspace will not work - Access application at http://localhost:3000/ + +### Deployment + +This application is hosted on our nomad cluster. Deploy using `bin/deploy`, e.g.: + +``` +BRANCH=[my_branch] ./bin/deploy staging +``` + +The container must be built via github actions before deploy can succeed. It will keep trying if the container isn't ready yet. + +#### Testing Production Install + +You can test the docker container by running `docker compose up` and going to `http://localhost:3000` diff --git a/bin/deploy b/bin/deploy new file mode 100755 index 0000000..4d3dfd3 --- /dev/null +++ b/bin/deploy @@ -0,0 +1,66 @@ +#!/bin/bash +ENV=$1 +BRANCH_NAME="${BRANCH:-main}" +REPOSITORY="${REPO:-abid}" +JOB_NAME="${JOBNAME:-adid}" + +# Make sure we're on VPN +if ! nslookup nomad-host-prod1.lib.princeton.edu 2>&1 > /dev/null +then + echo "Unable to connect to nomad-host-prod1. Ensure you're on VPN." + exit 1 +fi + +## Get Github Token +if ! command -v gh &> /dev/null +then + if [ -z "$GITHUB_TOKEN" ] + then + echo "gh must be installed or a token passed with GITHUB_TOKEN. Run 'brew install gh'." + exit 1 + fi +fi + +GH_TOKEN="${GITHUB_TOKEN:-$(gh auth token 2> /dev/null)}" + +if [ "$GH_TOKEN" = "" ] +then + echo "Github token not set. Run 'gh auth login' and follow the directions." + exit 1 +fi + +if [[ -z "${ENV}" ]]; +then + echo "Missing Environment. Command: 'BRANCH=main ./bin/deploy staging'." + exit +fi + +# Create Github Deployment +DEPLOY_OUTPUT=$(curl -s -X POST -H "Accept: application/vnd.github+json-H" -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Bearer ${GH_TOKEN}" --data "{\"ref\":\"${BRANCH_NAME}\",\"description\":\"Deploy from Nomad script\", \"auto_merge\": false, \"environment\": \"${ENV}\", \"required_contexts\": [] }" "https://api.github.com/repos/pulibrary/${REPOSITORY}/deployments") +regex='"id": ([0-9]+),' +[[ $DEPLOY_OUTPUT =~ $regex ]] +DEPLOY_ID=${BASH_REMATCH[1]} + +if [[ -z "${DEPLOY_ID}" ]] +then + echo "Unable to fetch Deploy ID." + exit 1 +fi + +# Create "Started" Deployment Status +curl -s -X POST -H "Accept: application/vnd.github+json-H" -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Bearer ${GH_TOKEN}" --data "{\"environment\":\"${ENV}\",\"state\":\"in_progress\",\"log_url\": \"https://nomad.lib.princeton.edu/ui/jobs/${JOB_NAME}-${ENV}\", \"description\":\"Deployment started.\"}" "https://api.github.com/repos/pulibrary/${REPOSITORY}/deployments/${DEPLOY_ID}/statuses" > /dev/null + +# Deploy using nomad-host-prod1, which has the nomad management key. +ssh deploy@nomad-host-prod1.lib.princeton.edu << EOF + curl -H 'Cache-Control: no-cache' -s "https://raw.githubusercontent.com/pulibrary/${REPOSITORY}/${BRANCH_NAME}/config/deploy/${ENV}.hcl" | nomad job run -var "branch_or_sha=sha-$(git ls-remote https://github.com/pulibrary/${REPOSITORY}.git ${BRANCH_NAME} | awk '{ print substr($1,1,7) }')" - +EOF +retcode=$? + +if [ $retcode -eq 0 ] +then + # Create "Completed Successfully" Deployment Status + curl -s -X POST -H "Accept: application/vnd.github+json-H" -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Bearer ${GH_TOKEN}" --data "{\"environment\":\"${ENV}\",\"state\":\"success\",\"log_url\": \"https://nomad.lib.princeton.edu/ui/jobs/${JOB_NAME}-${ENV}\", \"description\":\"Deployment finished successfully.\"}" "https://api.github.com/repos/pulibrary/${REPOSITORY}/deployments/${DEPLOY_ID}/statuses" > /dev/null +else + # Create "Failed" Deployment Status + curl -s -X POST -H "Accept: application/vnd.github+json-H" -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Bearer ${GH_TOKEN}" --data "{\"environment\":\"${ENV}\",\"state\":\"failure\",\"log_url\": \"https://nomad.lib.princeton.edu/ui/jobs/${JOB_NAME}-${ENV}\", \"description\":\"Deployment failed.\"}" "https://api.github.com/repos/pulibrary/${REPOSITORY}/deployments/${DEPLOY_ID}/statuses" > /dev/null +fi diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100755 index 0000000..de9ae51 --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,14 @@ +#!/bin/bash -e + +# Enable jemalloc for reduced memory usage and latency. +if [ -z "${LD_PRELOAD+x}" ]; then + LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) + export LD_PRELOAD +fi + +# If running the production procfile then create or migrate existing database +if [ "${@: -3:1}" == "foreman" ] && [ "${@: -2:1}" == "start" ] && [ "${@: -1:1}" == "--procfile=Procfile.prod" ]; then + ./bin/rails db:prepare +fi + +exec "${@}" diff --git a/config/deploy/production.hcl b/config/deploy/production.hcl new file mode 100644 index 0000000..a5d0fdc --- /dev/null +++ b/config/deploy/production.hcl @@ -0,0 +1,61 @@ +variable "branch_or_sha" { + type = string + default = "main" +} +job "abid-production" { + region = "global" + datacenters = ["dc1"] + node_pool = "production" + type = "service" + group "web" { + count = 2 + network { + port "http" { to = 3000 } + } + service { + port = "http" + check { + type = "http" + port = "http" + path = "/health.json" + interval = "10s" + timeout = "1s" + } + } + task "webserver" { + driver = "podman" + config { + image = "ghcr.io/pulibrary/abid:${ var.branch_or_sha }" + ports = ["http"] + force_pull = true + } + resources { + cpu = 1000 + memory = 500 + } + template { + destination = "${NOMAD_SECRETS_DIR}/env.vars" + env = true + change_mode = "restart" + data = <