Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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*
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ name: CI

on:
push:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
packages: write

jobs:
lint_and_scan_ruby:
Expand Down Expand Up @@ -114,3 +123,61 @@ jobs:
--order random \
--format documentation \
--format RSpec::Github::Formatter

deploy:
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
needs: [lint_and_scan_ruby, test]
runs-on: ubuntu-latest
environment: production
concurrency:
group: deploy-production
cancel-in-progress: false

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Login to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: |
ghcr.io/convus/convus_webapp:${{ github.sha }}
ghcr.io/convus/convus_webapp:latest
labels: service=convus_webapp
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Set up SSH
uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

- name: Deploy with Kamal
run: |
cp .kamal/secrets-ci .kamal/secrets
bundle exec kamal deploy --skip-push
env:
KAMAL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
SECRET_KEY_BASE: ${{ secrets.SECRET_KEY_BASE }}
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
HONEYBADGER_API_KEY: ${{ secrets.HONEYBADGER_API_KEY }}
DEVISE_SECRET_KEY: ${{ secrets.DEVISE_SECRET_KEY }}
DEVISE_PEPPER: ${{ secrets.DEVISE_PEPPER }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ yarn-error.log
/node_modules
/app/assets/builds/*
!/app/assets/builds/.keep
esbuild_error

# Ignore master key for decrypting credentials and more.
/config/master.key
Expand All @@ -47,3 +48,7 @@ yarn-error.log

# Workspace ID assigned by bin/workspace_setup
.workspace_id

# Ignore Kamal secrets.
/.kamal/secrets*
!/.kamal/secrets-ci
7 changes: 7 additions & 0 deletions .kamal/hooks/post-deploy
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

bundle exec honeybadger deploy \
--repository https://github.com/convus/convus_webapp \
--revision $KAMAL_VERSION \
--environment production \
--user $KAMAL_PERFORMER
9 changes: 9 additions & 0 deletions .kamal/secrets-ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# CI secrets file — all values come from GitHub Actions env vars.
# Kamal's Dotenv parser requires explicit assignments (no shell conditionals).
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
RAILS_MASTER_KEY=$RAILS_MASTER_KEY
SECRET_KEY_BASE=$SECRET_KEY_BASE
HONEYBADGER_API_KEY=$HONEYBADGER_API_KEY
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
DEVISE_SECRET_KEY=$DEVISE_SECRET_KEY
DEVISE_PEPPER=$DEVISE_PEPPER
74 changes: 74 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# syntax=docker/dockerfile:1
# check=error=true

# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t convus_webapp .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name convus_webapp convus_webapp

# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=4.0.2
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
WORKDIR /rails

# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment variables and enable jemalloc for reduced memory usage and latency.
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development" \
LD_PRELOAD="/usr/local/lib/libjemalloc.so"

# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev libyaml-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install application gems
COPY vendor/* ./vendor/
COPY Gemfile Gemfile.lock ./

RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
bundle exec bootsnap precompile -j 1 --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times.
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
RUN bundle exec bootsnap precompile -j 1 app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 REDIS_URL="redis://localhost:6379" ./bin/rails assets:precompile

# Final stage for app image
FROM base

# 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
USER 1000:1000

# Copy built artifacts: gems, application
COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --chown=rails:rails --from=build /rails /rails

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ ruby "4.0.6"
gem "rails"

gem "puma" # Use Puma as the app server
gem "thruster", require: false # Asset compression and caching for Puma
gem "kamal", require: false # Deploy with Kamal
gem "rack-cors" # Make cors requests

# database stuff
Expand Down
Loading