Skip to content

Commit 2d15090

Browse files
committed
Adds qa deployi with kamal.
1 parent 725920b commit 2d15090

15 files changed

Lines changed: 538 additions & 4 deletions

File tree

.dockerignore

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2+
3+
# Ignore git directory.
4+
/.git/
5+
/.gitignore
6+
7+
# Ignore bundler config.
8+
/.bundle
9+
10+
# Ignore all environment files.
11+
/.env*
12+
13+
# Ignore all default key files.
14+
/config/master.key
15+
/config/credentials/*.key
16+
17+
# Ignore all logfiles and tempfiles.
18+
/log/*
19+
/tmp/*
20+
!/log/.keep
21+
!/tmp/.keep
22+
23+
# Ignore pidfiles, but keep the directory.
24+
/tmp/pids/*
25+
!/tmp/pids/.keep
26+
27+
# Ignore storage (uploaded files in development and any SQLite databases).
28+
/storage/*
29+
!/storage/.keep
30+
/tmp/storage/*
31+
!/tmp/storage/.keep
32+
33+
# Ignore assets.
34+
/node_modules/
35+
/app/assets/builds/*
36+
!/app/assets/builds/.keep
37+
/public/assets
38+
39+
# Ignore CI service files.
40+
/.github
41+
42+
# Ignore Kamal files.
43+
/config/deploy*.yml
44+
/.kamal
45+
46+
# Ignore development files
47+
/.devcontainer
48+
49+
# Ignore Docker-related files
50+
/.dockerignore
51+
/Dockerfile*

.kamal/hooks/post-deploy

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env sh
2+
3+
# Notifies Honeybadger of a deploy (or rollback), analogous to the
4+
# dlss-capistrano-docker honeybadger.rake task:
5+
# https://github.com/sul-dlss/dlss-capistrano-docker/blob/main/lib/dlss/docker/capistrano/tasks/honeybadger.rake
6+
#
7+
# These environment variables are available:
8+
# KAMAL_RECORDED_AT
9+
# KAMAL_PERFORMER
10+
# KAMAL_VERSION
11+
# KAMAL_HOSTS
12+
# KAMAL_ROLES (if set)
13+
# KAMAL_DESTINATION (if set)
14+
# KAMAL_RUNTIME
15+
# KAMAL_COMMAND
16+
# KAMAL_SUBCOMMAND
17+
18+
set -e
19+
20+
REPO_URL=$(git config --get remote.origin.url)
21+
API_KEY=$(vault kv get -field=content puppet/application/argo-b3/honeybadger-api-key)
22+
23+
curl --no-progress-meter --data \
24+
"deploy[environment]=${KAMAL_DESTINATION}&deploy[local_username]=$(whoami)&deploy[revision]=${KAMAL_VERSION}&deploy[repository]=${REPO_URL}&api_key=${API_KEY}" \
25+
https://api.honeybadger.io/v1/deploys
26+
27+
echo "Honeybadger notification complete."

.kamal/secrets-common

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Secrets shared across all destinations (destination-specific secrets go in
2+
# .kamal/secrets.<destination>, e.g. .kamal/secrets.qa). All secrets should be
3+
# pulled from either password manager, ENV, or a file. DO NOT ENTER RAW
4+
# CREDENTIALS HERE! This file needs to be safe for git.
5+
6+
# No registry credentials needed: config/deploy.yml uses Kamal's local registry mode
7+
# (registry.server: localhost:5555), which requires no login.
8+
9+
# Secrets are pulled from Vault. Prompt for `vault login -method oidc` if the
10+
# current token is missing or expired, before any of the `vault kv get` calls
11+
# below (and in .kamal/secrets.<destination>) run.
12+
13+
_VAULT_LOGIN_CHECK=$(vault token lookup >/dev/null 2>&1 || vault login -method oidc >&2)
14+
HONEYBADGER_API_KEY=$(vault kv get -field=content puppet/application/argo-b3/honeybadger-api-key)

.kamal/secrets.qa

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Secrets specific to the qa destination. Loaded alongside .kamal/secrets-common
2+
# (shared secrets go there instead). All secrets should be pulled from either
3+
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file
4+
# needs to be safe for git.
5+
6+
# Secrets are pulled from Vault. Before deploying, authenticate with:
7+
# vault login -method oidc
8+
9+
SECRET_KEY_BASE=$(vault kv get -field=content puppet/application/argo-b3/qa/secret-key-base)
10+
SETTINGS__DOR_SERVICES__TOKEN=$(vault kv get -field=content puppet/application/argo-b3/qa/dor-services-token)
11+
SETTINGS__PRESERVATION_CATALOG__TOKEN=$(vault kv get -field=content puppet/application/argo-b3/qa/preservation-catalog-token)
12+
DATABASE_PASSWORD=$(vault kv get -field=content puppet/application/argo-b3/qa/lyberadmin-db-pwd)

Dockerfile

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# syntax=docker/dockerfile:1
2+
# check=error=true
3+
4+
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
5+
# docker build -t argo_b3 .
6+
# docker run -d -p 80:80 --name argo_b3 argo_b3
7+
8+
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
9+
10+
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
11+
ARG RUBY_VERSION=3.4.2
12+
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
13+
14+
# Make sure NODE_VERSION matches the Node version in .node-version
15+
ARG NODE_VERSION=22.12.0
16+
ARG YARN_VERSION=1.22.22
17+
18+
# Rails app lives here
19+
WORKDIR /rails
20+
21+
# Install base packages
22+
RUN apt-get update -qq && \
23+
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
24+
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
25+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
26+
27+
# Install JS runtime and package manager needed by cssbundling-rails (sass/postcss via yarn)
28+
ENV PATH=/usr/local/node/bin:$PATH
29+
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
30+
/tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
31+
npm install -g yarn@$YARN_VERSION && \
32+
rm -rf /tmp/node-build-master
33+
34+
# Set production environment variables and enable jemalloc for reduced memory usage and latency.
35+
ENV RAILS_ENV="production" \
36+
BUNDLE_DEPLOYMENT="1" \
37+
BUNDLE_PATH="/usr/local/bundle" \
38+
BUNDLE_WITHOUT="development" \
39+
LD_PRELOAD="/usr/local/lib/libjemalloc.so"
40+
41+
# Throw-away build stage to reduce size of final image
42+
FROM base AS build
43+
44+
# Install packages needed to build gems
45+
RUN apt-get update -qq && \
46+
apt-get install --no-install-recommends -y build-essential git libvips libyaml-dev pkg-config libpq-dev && \
47+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
48+
49+
# Install application gems
50+
COPY vendor/* ./vendor/
51+
COPY Gemfile Gemfile.lock ./
52+
53+
RUN bundle install && \
54+
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
55+
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
56+
bundle exec bootsnap precompile -j 1 --gemfile
57+
58+
# Install node modules
59+
COPY package.json yarn.lock ./
60+
RUN yarn install --frozen-lockfile
61+
62+
# Copy application code
63+
COPY . .
64+
65+
# Precompile bootsnap code for faster boot times.
66+
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
67+
RUN bundle exec bootsnap precompile -j 1 app/ lib/
68+
69+
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
70+
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
71+
72+
73+
74+
75+
# Final stage for app image
76+
FROM base
77+
78+
# Run and own only the runtime files as a non-root user for security. Matches
79+
# lyberadmin's uid:gid on the Puppet-managed deploy hosts (see config/deploy.qa.yml)
80+
# so no runtime --user override is needed there.
81+
RUN groupadd --system --gid 503 lyberadmin && \
82+
useradd lyberadmin --uid 503 --gid 503 --create-home --shell /bin/bash
83+
USER 503:503
84+
85+
# Copy built artifacts: gems, application
86+
COPY --chown=lyberadmin:lyberadmin --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
87+
COPY --chown=lyberadmin:lyberadmin --from=build /rails /rails
88+
89+
# Entrypoint prepares the database.
90+
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
91+
92+
# Start server via Thruster by default, this can be overwritten at runtime
93+
EXPOSE 80
94+
CMD ["./bin/thrust", "./bin/rails", "server"]

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ gem 'propshaft'
1010
gem 'pg'
1111
# Use the Puma web server [https://github.com/puma/puma]
1212
gem 'puma', '>= 5.0'
13+
# HTTP/2 proxy for Puma, for X-Sendfile asset serving and HTTP caching [https://github.com/basecamp/thruster]
14+
gem 'thruster', require: false
1315
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
1416
gem 'importmap-rails'
1517
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
@@ -104,4 +106,5 @@ group :deployment do
104106
gem 'capistrano-passenger', require: false
105107
gem 'capistrano-rails', require: false
106108
gem 'dlss-capistrano', require: false
109+
gem 'kamal', require: false
107110
end

Gemfile.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,86 @@ A dashboard for SolidQueue background jobs is available at http://localhost:3000
4949

5050
In development, available at http://localhost:3000/lookbook
5151

52-
## Deployment
52+
## Deployment with Kamal
53+
* See `--hosts` to run only on specific hosts.
54+
* See `--roles` to run only for specific roles (e.g., `web` or `job`)
5355

54-
NOTE: The application is deployed continuously by our on-prem Jenkins service (`sul-ci-prod`) to the `poc` environment on every merge to `main`. See `Jenkinsfile` for how that is wired up.
56+
Note:
57+
* Honeybadger deploy notifications are performs in `.kamal/hooks/post-deploy`.
58+
* Secrets are retrieved directly using the Vault CLI. See `.kamal/secrets-common` and environment specific secrets files.
59+
* The Dockerfile configures the lyberadmin (50:503) user to match the host server.
60+
* `/workspace/bulk` and `/var/log/argo` are shared with the containers.
61+
* The docker image is build on the host server (AMD64) to avoid emulation on the developer Mac.
62+
63+
64+
### Deploy
65+
```
66+
bin/kamal-otk qa deploy
67+
```
68+
69+
This will build and deploy the local, committed code.
70+
71+
#### Rollback
72+
```
73+
bin/kamal-otk qa app containers -q
74+
bin/kamal-otk qa rollback e5d9d7c2b898289dfbc5f7f1334140d984eedae4
75+
```
76+
77+
Use `app containers -q` to get the image ids of the containers to roll back to.
78+
79+
#### Stop / start
80+
```
81+
bin/kamal-otk qa app stop
82+
bin/kamal-otk qa app start
83+
```
84+
85+
#### Maintenance
86+
```
87+
bin/kamal-otk qa app maintenance --message "Wait for it..."
88+
bin/kamal-otk qa app live
89+
```
90+
91+
### Monitoring
92+
93+
#### App status
94+
```
95+
bin/kamal-otk qa details
96+
```
97+
98+
#### Container status
99+
```
100+
bin/kamal-otk qa app containers
101+
```
102+
103+
#### Deployed version
104+
```
105+
bin/kamal-otk qa app version
106+
```
107+
108+
#### Logs
109+
```
110+
bin/kamal-otk qa app logs -f
111+
```
112+
113+
* For log filtering options, see `bin/kamal-otk qa app logs --help` for log filtering options.
114+
* For log rotation, see `logging` in `deploy.yml`.
115+
* The application also logs to `/var/log`.
116+
117+
### Interacting
118+
See https://kamal-deploy.org/docs/commands/running-commands-on-servers/
119+
120+
#### With the server
121+
```
122+
bin/kamal-otk qa server exec -i "bash"
123+
bin/kamal-otk qa server exec "docker -v"
124+
```
125+
126+
#### With a container
127+
```
128+
bin/kamal-otk qa console
129+
bin/kamal-otk qa db
130+
bin/kamal-otk qa shell
131+
```
55132

56133
## Testing
57134

bin/kamal

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
#
5+
# This file was generated by Bundler.
6+
#
7+
# The application 'kamal' is installed as part of a gem, and
8+
# this file is here to facilitate running it.
9+
#
10+
11+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12+
13+
require "rubygems"
14+
require "bundler/setup"
15+
16+
load Gem.bin_path("kamal", "kamal")

0 commit comments

Comments
 (0)