From b29a47264fe0375b061e5deb0b10a8fdf806493b Mon Sep 17 00:00:00 2001 From: Martin Rucci Date: Mon, 3 Feb 2025 15:55:54 -0300 Subject: [PATCH] adds required changes for kamal 2.4 --- .github/workflows/deploy-production.yml | 22 +--- .github/workflows/deploy-staging.yml | 20 +-- .kamal/secrets | 20 +++ .kamal/secrets.staging | 18 +++ Dockerfile | 69 ++++------ bin/bundle | 17 +-- bin/docker-entrypoint | 15 +-- bin/kamal | 27 ++++ bin/setup | 14 +- bin/thrust | 5 + config/database.yml | 58 ++++++++- config/deploy.staging.yml | 163 ++++++++++++++---------- config/deploy.yml | 161 +++++++++++++---------- 13 files changed, 369 insertions(+), 240 deletions(-) create mode 100644 .kamal/secrets create mode 100644 .kamal/secrets.staging create mode 100644 bin/kamal create mode 100755 bin/thrust diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 6a83a92..f6ade59 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -1,8 +1,7 @@ name: Deploy to production env: - RAILS_ENV: production - SSH_PRODUCTION_PRIVATE_KEY: ${{ secrets.SSH_PRODUCTION_PRIVATE_KEY }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_REGION }} @@ -12,9 +11,9 @@ on: workflow_dispatch: inputs: branch: - description: "Branch to deploy" + description: 'Branch to deploy' required: true - default: "main" + default: 'main' jobs: build_and_deploy: @@ -29,7 +28,7 @@ jobs: uses: webfactory/ssh-agent@v0.7.0 with: ssh-private-key: | - ${{ env.SSH_PRODUCTION_PRIVATE_KEY }} + ${{ env.SSH_PRIVATE_KEY }} - name: Install docker buildx uses: docker/setup-buildx-action@v2 @@ -49,13 +48,6 @@ jobs: - name: Install kamal run: gem install kamal - - name: Run kamal envify targeting production - run: kamal envify - - - name: Run Kamal deploy targeting production - env: - KAMAL_ENV: ${{ env.RAILS_ENV }} - RAILS_ENV: ${{ env.RAILS_ENV }} - AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }} - DOCKER_BUILDKIT: 1 - run: kamal deploy + - name: Deploy to Production + run: | + kamal deploy diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 365d861..aba921d 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -1,7 +1,6 @@ name: Deploy to staging env: - RAILS_ENV: staging SSH_STAGING_PRIVATE_KEY: ${{ secrets.SSH_STAGING_PRIVATE_KEY }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -12,16 +11,16 @@ on: workflow_dispatch: inputs: branch: - description: "Branch to deploy" + description: 'Branch to deploy' required: true - default: "main" + default: 'main' jobs: build_and_deploy: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ inputs.branch }} @@ -49,13 +48,6 @@ jobs: - name: Install kamal run: gem install kamal - - name: Run kamal envify targeting staging - run: kamal envify -d staging - - - name: Run Kamal deploy targeting staging - env: - KAMAL_ENV: ${{ env.RAILS_ENV }} - RAILS_ENV: ${{ env.RAILS_ENV }} - AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }} - DOCKER_BUILDKIT: 1 - run: kamal deploy -d staging + - name: Deploy to Staging + run: | + kamal deploy -d staging diff --git a/.kamal/secrets b/.kamal/secrets new file mode 100644 index 0000000..946dc37 --- /dev/null +++ b/.kamal/secrets @@ -0,0 +1,20 @@ +# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, +# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either +# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. + +# Example of extracting secrets from 1password (or another compatible pw manager) +SECRETS=$(kamal secrets fetch --adapter aws_secrets_manager --account default --secret-id production/rails_api) + +DB_HOST=$(kamal secrets extract DB_HOST ${SECRETS}) +SERVER_HOST=$(kamal secrets extract SERVER_HOST ${SECRETS}) +JWT_SECRET=$(kamal secrets extract JWT_SECRET ${SECRETS}) +FRONTEND_URL=$(kamal secrets extract FRONTEND_URL ${SECRETS}) +DATABASE_URL=$(kamal secrets extract DATABASE_URL ${SECRETS}) +SIDEKIQ_REDIS_URL=$(kamal secrets extract SIDEKIQ_REDIS_URL ${SECRETS}) +S3_ACCESS_KEY_ID=$(kamal secrets extract S3_ACCESS_KEY_ID ${SECRETS}) +S3_ACCESS_KEY=$(kamal secrets extract S3_ACCESS_KEY ${SECRETS}) +S3_REGION=$(kamal secrets extract S3_REGION ${SECRETS}) +S3_BUCKET=$(kamal secrets extract S3_BUCKET ${SECRETS}) +SECRET_KEY_BASE=$(kamal secrets extract SECRET_KEY_BASE ${SECRETS}) +POSTGRES_PASSWORD=$(kamal secrets extract POSTGRES_PASSWORD ${SECRETS}) + diff --git a/.kamal/secrets.staging b/.kamal/secrets.staging new file mode 100644 index 0000000..71d805d --- /dev/null +++ b/.kamal/secrets.staging @@ -0,0 +1,18 @@ +# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets, +# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either +# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. + +# Example of extracting secrets from 1password (or another compatible pw manager) +SECRETS=$(kamal secrets fetch --adapter aws_secrets_manager --account default --secret-id staging/rails_api) + +SERVER_HOST=$(kamal secrets extract SERVER_HOST ${SECRETS}) +JWT_SECRET=$(kamal secrets extract JWT_SECRET ${SECRETS}) +FRONTEND_URL=$(kamal secrets extract FRONTEND_URL ${SECRETS}) +DATABASE_URL=$(kamal secrets extract DATABASE_URL ${SECRETS}) +SIDEKIQ_REDIS_URL=$(kamal secrets extract SIDEKIQ_REDIS_URL ${SECRETS}) +S3_ACCESS_KEY_ID=$(kamal secrets extract S3_ACCESS_KEY_ID ${SECRETS}) +S3_ACCESS_KEY=$(kamal secrets extract S3_ACCESS_KEY ${SECRETS}) +S3_REGION=$(kamal secrets extract S3_REGION ${SECRETS}) +S3_BUCKET=$(kamal secrets extract S3_BUCKET ${SECRETS}) +SECRET_KEY_BASE=$(kamal secrets extract SECRET_KEY_BASE ${SECRETS}) +POSTGRES_PASSWORD=$(kamal secrets extract POSTGRES_PASSWORD ${SECRETS}) diff --git a/Dockerfile b/Dockerfile index 729a941..4da035e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,62 +2,50 @@ # Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile ARG RUBY_VERSION=3.3.6 -FROM ruby:$RUBY_VERSION-slim as base - -ARG RAILS_ENV=production +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 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + # Set production environment -ENV BUNDLE_DEPLOYMENT="1" \ +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ BUNDLE_PATH="/usr/local/bundle" \ - BUNDLE_WITHOUT="development:test" \ - RAILS_ENV=$RAILS_ENV - -# Update gems and bundler -RUN gem update --system --no-document && \ - gem install -N bundler - + BUNDLE_WITHOUT="development" # Throw-away build stage to reduce size of final image -FROM base as build +FROM base AS build # Install packages needed to build gems -RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \ - --mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \ - apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential libpq-dev libvips +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential git pkg-config && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install application gems -COPY --link Gemfile Gemfile.lock .ruby-version ./ -RUN --mount=type=cache,id=bld-gem-cache,sharing=locked,target=/srv/vendor \ - bundle config set app_config .bundle && \ - bundle config set path /srv/vendor && \ - bundle install && \ - bundle exec bootsnap precompile --gemfile && \ - bundle clean && \ - mkdir -p vendor && \ - bundle config set path vendor && \ - cp -ar /srv/vendor . +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 application code -COPY --link . . +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 -ARG KAMAL_VERSION -# Install packages needed for deployment -RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \ - --mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \ - apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libvips postgresql-client + +# Final stage for app image +FROM base # Copy built artifacts: gems, application COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}" @@ -66,15 +54,12 @@ 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 -R 1000:1000 db log storage tmp + chown -R rails:rails db log storage tmp USER 1000:1000 -# Set the version of Kamal deployment -ENV KAMAL_VERSION=$KAMAL_VERSION - # Entrypoint prepares the database. ENTRYPOINT ["/rails/bin/docker-entrypoint"] -# Start the server by default, this can be overwritten at runtime -EXPOSE 3000 -CMD ["./bin/rails", "server"] +# Start server via Thruster by default, this can be overwritten at runtime +EXPOSE 80 +CMD ["./bin/thrust", "./bin/rails", "server"] diff --git a/bin/bundle b/bin/bundle index 981e650..50da5fd 100755 --- a/bin/bundle +++ b/bin/bundle @@ -27,7 +27,7 @@ m = Module.new do bundler_version = nil update_index = nil ARGV.each_with_index do |a, i| - if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN + if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) bundler_version = a end next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ @@ -47,7 +47,7 @@ m = Module.new do def lockfile lockfile = case File.basename(gemfile) - when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) + when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") else "#{gemfile}.lock" end File.expand_path(lockfile) @@ -62,8 +62,9 @@ m = Module.new do def bundler_requirement @bundler_requirement ||= - env_var_version || cli_arg_version || - bundler_requirement_for(lockfile_version) + env_var_version || + cli_arg_version || + bundler_requirement_for(lockfile_version) end def bundler_requirement_for(version) @@ -71,13 +72,7 @@ m = Module.new do bundler_gem_version = Gem::Version.new(version) - requirement = bundler_gem_version.approximate_recommendation - - return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") - - requirement += ".a" if bundler_gem_version.prerelease? - - requirement + bundler_gem_version.approximate_recommendation end def load_bundler! diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index ab9f8b2..57567d6 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -1,15 +1,14 @@ #!/bin/bash -e -# If running the rails server then create or migrate existing database -if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then - ./bin/rails db:prepare +# 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 [ -f tmp/pids/server.pid ]; then - # if there's no process with the server.pid then remove the file - if ! ps $(cat tmp/pids/server.pid) > /dev/null; then - rm tmp/pids/server.pid - fi +# If running the rails server then create or migrate existing database +if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then + ./bin/rails db:prepare fi exec "${@}" diff --git a/bin/kamal b/bin/kamal new file mode 100644 index 0000000..cbe59b9 --- /dev/null +++ b/bin/kamal @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'kamal' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("kamal", "kamal") diff --git a/bin/setup b/bin/setup index d267cde..a632ff9 100755 --- a/bin/setup +++ b/bin/setup @@ -1,8 +1,8 @@ #!/usr/bin/env ruby require "fileutils" +# path to your application root. APP_ROOT = File.expand_path("..", __dir__) -APP_NAME = "rails-api" def system!(*args) system(*args, exception: true) @@ -14,7 +14,6 @@ FileUtils.chdir APP_ROOT do # Add necessary setup steps to this file. puts "== Installing dependencies ==" - system! "gem install bundler --conservative" system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" @@ -28,10 +27,9 @@ FileUtils.chdir APP_ROOT do puts "\n== Removing old logs and tempfiles ==" system! "bin/rails log:clear tmp:clear" - puts "\n== Restarting application server ==" - system! "bin/rails restart" - - # puts "\n== Configuring puma-dev ==" - # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" - # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end end diff --git a/bin/thrust b/bin/thrust new file mode 100755 index 0000000..36bde2d --- /dev/null +++ b/bin/thrust @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("thruster", "thrust") diff --git a/config/database.yml b/config/database.yml index 35df2f6..6f4e3e9 100644 --- a/config/database.yml +++ b/config/database.yml @@ -21,9 +21,27 @@ default: &default # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> +# sqlite: &sqlite +# adapter: sqlite3 +# pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> +# timeout: 5000 + development: - <<: *default - database: rails_api_development + primary: + <<: *default + database: rails_api_development + # cache: + # <<: *sqlite + # database: storage/rails_api_development_cache.sqlite3 + # migrations_paths: db/cache_migrate + # cable: + # <<: *sqlite + # database: storage/rails_api_development_cable.sqlite3 + # migrations_paths: db/cable_migrate + # queue: + # <<: *sqlite + # database: storage/rails_api_development_queue.sqlite3 + # migrations_paths: db/queue_migrate # The specified database role being used to connect to postgres. # To create additional roles in postgres see `$ createuser --help`. @@ -80,7 +98,39 @@ test: # for a full overview on how database connection configuration can be specified. # production: - <<: *default + primary: &primary_production + <<: *default + database: rails_api_production + username: rails_api + password: <%= ENV["RAILS_API_DATABASE_PASSWORD"] %> + # cache: + # <<: *sqlite + # database: storage/life_task_planner_production_cache.sqlite3 + # migrations_paths: db/cache_migrate + # cable: + # <<: *sqlite + # database: storage/life_task_planner_production_cable.sqlite3 + # migrations_paths: db/cable_migrate + # queue: + # <<: *sqlite + # database: storage/life_task_planner_production_queue.sqlite3 + # migrations_paths: db/queue_migrate staging: - <<: *default + primary: &primary_staging + <<: *default + database: rails_api_staging + username: rails_api + password: <%= ENV["RAILS_API_DATABASE_PASSWORD"] %> + # cache: + # <<: *sqlite + # database: storage/life_task_planner_staging_cache.sqlite3 + # migrations_paths: db/cache_migrate + # cable: + # <<: *sqlite + # database: storage/life_task_planner_staging_cable.sqlite3 + # migrations_paths: db/cable_migrate + # queue: + # <<: *sqlite + # database: storage/life_task_planner_staging_queue.sqlite3 + # migrations_paths: db/queue_migrate diff --git a/config/deploy.staging.yml b/config/deploy.staging.yml index 92a0a11..063e8e3 100644 --- a/config/deploy.staging.yml +++ b/config/deploy.staging.yml @@ -1,24 +1,28 @@ # Name of your application. Used to uniquely configure containers. -service: rails-api +service: rails_api # Name of the container image. -image: rails-api +image: gogrow-dev/rails_api # Deploy to these servers. servers: web: - hosts: - - 127.0.0.1 - labels: - traefik.http.routers.rails_api.rule: Host(`api.rails-api-staging.com`) - traefik.http.routers.rails_api_secure.entrypoints: websecure - traefik.http.routers.rails_api_secure.rule: Host(`api.rails-api-staging.com`) - traefik.http.routers.rails_api_secure.tls: true - traefik.http.routers.rails_api_secure.tls.certresolver: letsencrypt - job: - hosts: - - 127.0.0.1 - cmd: bin/sidekiq + - 127.0.0.1 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs + +# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server). +# If using something like Cloudflare, it is recommended to set encryption mode +# in Cloudflare's SSL/TLS setting to "Full" to enable end-to-end encryption. +proxy: + ssl: true + hosts: + - www.rails-api.com + - rails-api.com + # kamal-proxy connects to your container over port 80, use `app_port` to specify a different port. + # app_port: 3000 # Credentials for your image host. registry: @@ -26,14 +30,12 @@ registry: username: AWS password: <%= %x(aws ecr get-login-password) %> +# Configure builder setup. +builder: + arch: amd64 + +# Inject ENV variables into containers (secrets come from .kamal/secrets). env: - clear: - RAILS_ENV: staging - SERVER_TIMING: true - RAILS_LOG_LEVEL: debug - SERVER_HOST: api.rails-api-staging.com - FRONTEND_URL: https://rails-api-staging.com - MAILER_SENDER: "Rails API " secret: - JWT_SECRET - DATABASE_URL @@ -45,55 +47,76 @@ env: - SIDEKIQ_PASSWORD - SIDEKIQ_REDIS_URL - SECRET_KEY_BASE + clear: + RAILS_ENV: staging + SERVER_TIMING: true + RAILS_LOG_LEVEL: debug + SERVER_HOST: api.rails-api-staging.com + FRONTEND_URL: https://rails-api-staging.com + MAILER_SENDER: "Rails API " -# Use a different ssh user than root -ssh: - user: ubuntu + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + # SOLID_QUEUE_IN_PUMA: true -# Configure builder setup. + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # Use shared_spaces_api-db for a db accessory server on same machine via local kamal docker network. + DB_HOST: rails_api-db + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. +aliases: + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole" + + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +# volumes: +# - 'rails_api_storage:/rails/storage' + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +# asset_path: /rails/public/assets + +# Configure the image builder. builder: - args: - RUBY_VERSION: <%= %x(cat .ruby-version).strip %> - RAILS_ENV: staging - KAMAL_VERSION: <%= %x(echo $KAMAL_VERSION).strip %> - multiarch: true - local: - arch: amd64 - cache: - type: registry - -# Configure custom arguments for Traefik logging to CloudWatch. -# logging: -# driver: awslogs -# options: -# awslogs-region: <%= ENV['AWS_REGION'] %> -# awslogs-group: staging/api -# awslogs-create-group: true -# tag: "{{.Name}}-{{.ID}}" - -# Configure custom arguments for Traefik logging. -traefik: - args: - log: true - log.level: ERROR - accesslog: true - accesslog.format: json - accesslog.filters.minduration: 10ms - entryPoints.web.address: ":80" - entryPoints.websecure.address: ":443" - entryPoints.web.http.redirections.entryPoint.to: websecure - entryPoints.web.http.redirections.entryPoint.scheme: https - entryPoints.web.http.redirections.entrypoint.permanent: true - certificatesResolvers.letsencrypt.acme.email: "dev@rails-api-staging.com" - certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json" - certificatesResolvers.letsencrypt.acme.httpchallenge: true - certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web - options: - publish: - - "443:443" - volume: - - "/letsencrypt/acme.json:/letsencrypt/acme.json" - -# Configure a custom healthcheck (default is /up on port 3000) -healthcheck: - path: /up + arch: amd64 + +# Use accessory services (secrets come from .kamal/secrets). +# +accessories: + db: + image: postgres:17.0 + host: 127.0.0.1 + port: 127.0.0.1:5432:5432 + env: + clear: + MYSQL_ROOT_HOST: '%' + secret: + - POSTGRES_PASSWORD + - POSTGRES_USER + - POSTGRES_DB + files: + # - config/mysql/production.cnf:/etc/mysql/my.cnf + - db/production.sql:/docker-entrypoint-initdb.d/setup.sql + volumes: + - /var/lib/postgresql/data:/var/lib/postgresql/data + redis: + image: redis:latest + port: 6379 + host: 127.0.0.1 + volumes: + - /var/lib/redis:/data diff --git a/config/deploy.yml b/config/deploy.yml index 95e5d89..34b176a 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -1,24 +1,28 @@ # Name of your application. Used to uniquely configure containers. -service: rails-api +service: rails_api # Name of the container image. -image: rails-api +image: gogrow-dev/rails_api # Deploy to these servers. servers: web: - hosts: - - 127.0.0.1 - labels: - traefik.http.routers.rails_api.rule: Host(`api.rails-api.com`) - traefik.http.routers.rails_api_secure.entrypoints: websecure - traefik.http.routers.rails_api_secure.rule: Host(`api.rails-api.com`) - traefik.http.routers.rails_api_secure.tls: true - traefik.http.routers.rails_api_secure.tls.certresolver: letsencrypt - job: - hosts: - - 127.0.0.1 - cmd: bin/sidekiq + - 127.0.0.1 + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs + +# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server). +# If using something like Cloudflare, it is recommended to set encryption mode +# in Cloudflare's SSL/TLS setting to "Full" to enable end-to-end encryption. +proxy: + ssl: true + hosts: + - www.rails-api.com + - rails-api.com + # kamal-proxy connects to your container over port 80, use `app_port` to specify a different port. + # app_port: 3000 # Credentials for your image host. registry: @@ -26,12 +30,12 @@ registry: username: AWS password: <%= %x(aws ecr get-login-password) %> +# Configure builder setup. +builder: + arch: amd64 + +# Inject ENV variables into containers (secrets come from .kamal/secrets). env: - clear: - RAILS_ENV: production - SERVER_HOST: api.rails-api.com - FRONTEND_URL: https://rails-api.com - MAILER_SENDER: "Rails API " secret: - JWT_SECRET - DATABASE_URL @@ -43,55 +47,76 @@ env: - SIDEKIQ_PASSWORD - SIDEKIQ_REDIS_URL - SECRET_KEY_BASE + clear: + RAILS_ENV: production + SERVER_TIMING: true + RAILS_LOG_LEVEL: debug + SERVER_HOST: api.rails-api.com + FRONTEND_URL: https://rails-api.com + MAILER_SENDER: "Rails API " -# Use a different ssh user than root -ssh: - user: ubuntu + # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. + # When you start using multiple servers, you should split out job processing to a dedicated machine. + # SOLID_QUEUE_IN_PUMA: true -# Configure builder setup. + # Set number of processes dedicated to Solid Queue (default: 1) + # JOB_CONCURRENCY: 3 + + # Set number of cores available to the application on each server (default: 1). + # WEB_CONCURRENCY: 2 + + # Match this to any external database server to configure Active Record correctly + # Use shared_spaces_api-db for a db accessory server on same machine via local kamal docker network. + DB_HOST: DB_HOST + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + +# Aliases are triggered with "bin/kamal ". You can overwrite arguments on invocation: +# "bin/kamal logs -r job" will tail logs from the first server in the job section. +aliases: + console: app exec --interactive --reuse "bin/rails console" + shell: app exec --interactive --reuse "bash" + logs: app logs -f + dbc: app exec --interactive --reuse "bin/rails dbconsole" + + +# Use a persistent storage volume for sqlite database files and local Active Storage files. +# Recommended to change this to a mounted volume path that is backed up off server. +# volumes: +# - 'rails_api_storage:/rails/storage' + +# Bridge fingerprinted assets, like JS and CSS, between versions to avoid +# hitting 404 on in-flight requests. Combines all files from new and old +# version inside the asset_path. +# asset_path: /rails/public/assets + +# Configure the image builder. builder: - args: - RUBY_VERSION: <%= %x(cat .ruby-version).strip %> - RAILS_ENV: production - KAMAL_VERSION: <%= %x(echo $KAMAL_VERSION).strip %> - multiarch: true - local: - arch: amd64 - cache: - type: registry - -# Configure custom arguments for Traefik logging to CloudWatch. -# logging: -# driver: awslogs -# options: -# awslogs-region: <%= ENV['AWS_REGION'] %> -# awslogs-group: production/api -# awslogs-create-group: true -# tag: "{{.Name}}-{{.ID}}" - -# Configure custom arguments for Traefik logging. -traefik: - args: - log: true - log.level: ERROR - accesslog: true - accesslog.format: json - accesslog.filters.minduration: 10ms - entryPoints.web.address: ":80" - entryPoints.websecure.address: ":443" - entryPoints.web.http.redirections.entryPoint.to: websecure - entryPoints.web.http.redirections.entryPoint.scheme: https - entryPoints.web.http.redirections.entrypoint.permanent: true - certificatesResolvers.letsencrypt.acme.email: "dev@rails-api.com" - certificatesResolvers.letsencrypt.acme.storage: "/letsencrypt/acme.json" - certificatesResolvers.letsencrypt.acme.httpchallenge: true - certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web - options: - publish: - - "443:443" - volume: - - "/letsencrypt/acme.json:/letsencrypt/acme.json" - -# Configure a custom healthcheck (default is /up on port 3000) -healthcheck: - path: /up + arch: amd64 + +# Use accessory services (secrets come from .kamal/secrets). +# +# accessories: +# db: +# image: postgres:17.0 +# host: 127.0.0.1 +# port: 127.0.0.1:5432:5432 +# env: +# clear: +# MYSQL_ROOT_HOST: '%' +# secret: +# - POSTGRES_PASSWORD +# - POSTGRES_USER +# - POSTGRES_DB +# files: +# # - config/mysql/production.cnf:/etc/mysql/my.cnf +# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql +# volumes: +# - /var/lib/postgresql/data:/var/lib/postgresql/data +# redis: +# image: redis:latest +# port: 6379 +# host: 127.0.0.1 +# volumes: +# - /var/lib/redis:/data