From ad2976daaad1a4cadd67d958f642f3ce764870e4 Mon Sep 17 00:00:00 2001 From: Brodie Date: Thu, 28 Nov 2024 22:35:41 +1100 Subject: [PATCH 1/8] Add new recipe how to deploy your next Inertia Rails app with Kamal and SSR support --- docs/.vitepress/config.mts | 6 + docs/cookbook/deploy-with-kamal.md | 222 +++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 docs/cookbook/deploy-with-kamal.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 5ee0f878..07968590 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -135,6 +135,12 @@ export default defineConfig({ { text: 'shadcn/ui', link: '/cookbook/integrating-shadcn-ui' }, ], }, + { + text: 'Deployment', + items: [ + { text: 'Deploy with Kamal', link: '/cookbook/deploy-with-kamal' }, + ], + }, ], }, ], diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md new file mode 100644 index 00000000..dd67e72f --- /dev/null +++ b/docs/cookbook/deploy-with-kamal.md @@ -0,0 +1,222 @@ +# Deploy with `Kamal` + +Rails 8 will ship with [Kamal](https://kamal-deploy.org/) preconfigured as the default deployment tool. +If your application does not require [SSR](/guide/server-side-rendering.md), you simply just need to +[update your asset_path](#update-asset-path-inconfig-deploy-yml), and deployment should work seamlessly. + +However, if you plan to configure your Inertia Rails application with [SSR](/guide/server-side-rendering.md) enabled, +a few additional tweaks may be required. This guide will walk you through the steps to quickly configure +[Kamal](https://kamal-deploy.org/) for deploying your next Inertia Rails application with +[SSR](/guide/server-side-rendering.md) support. + +> Note: This guide is based on Rails 8.0 and Kamal 2.3.0 at the time of writing. + + +## Update your Dockerfile + +It is crucial to ensure that the **_Install JavaScript dependencies_** step is executed in the **_base_** image. This +guarantees that the Node.js runtime is available for both the **_build_** stage and the **_runtime_** stage. + +```dockerfile +# 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 bn_quanlynhatro . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name bn_quanlynhatro bn_quanlynhatro + +# 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=3.3.6 +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 + +# Install JavaScript dependencies // [!code ++] +ARG NODE_VERSION=22.11.0 // [!code ++] +ARG YARN_VERSION=1.22.22 // [!code ++] +ENV PATH=/usr/local/node/bin:$PATH // [!code ++] +RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ // [!code ++] + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ // [!code ++] + npm install -g yarn@$YARN_VERSION && \ // [!code ++] + rm -rf /tmp/node-build-master // [!code ++] + +# Set production environment +ENV RAILS_ENV="production" \ + BUNDLE_DEPLOYMENT="1" \ + BUNDLE_PATH="/usr/local/bundle" \ + BUNDLE_WITHOUT="development" + +# Throw-away build stage to reduce size of final image +FROM base AS build + +# Install packages needed to build gems and node modules +RUN apt-get update -qq && \ + apt-get install --no-install-recommends -y build-essential git libpq-dev node-gyp pkg-config python-is-python3 && \ + rm -rf /var/lib/apt/lists /var/cache/apt/archives + +# Install JavaScript dependencies // [!code --] +ARG NODE_VERSION=22.11.0 // [!code --] +ARG YARN_VERSION=1.22.22 // [!code --] +ENV PATH=/usr/local/node/bin:$PATH // [!code --] +RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ // [!code --] + /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ // [!code --] + npm install -g yarn@$YARN_VERSION && \ // [!code --] + rm -rf /tmp/node-build-master // [!code --] + +# Install application gems +COPY .ruby-version Gemfile Gemfile.lock ./ +RUN bundle install && \ + rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ + bundle exec bootsnap precompile --gemfile + +# Install node modules +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# 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 + +RUN rm -rf node_modules + + +# Final stage for app image +FROM base + +# 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 -R rails:rails db log storage tmp +USER 1000:1000 + +# 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"] +``` + + +## Setup server role to run SSR server in `config/deploy.yml` + +The Node-based Inertia SSR server is used to pre-render pages on the server before sending them to the client. +The `vite_ssr` role ensures that the SSR server runs separately from the main Rails app server. + +```yml +# Deploy to these servers. +servers: + web: + - 192.168.0.1 + vite_ssr: // [!code ++] + hosts: // [!code ++] + - 192.168.0.1 // [!code ++] + cmd: bundle exec vite ssr // [!code ++] + options: // [!code ++] + network-alias: vite_ssr // [!code ++] + # job: + # hosts: + # - 192.168.0.1 + # cmd: bin/jobs +``` + + +## Specify the Vite server in `config/deploy.yml` + +The Rails app needs to know where to send SSR requests. Add the `VITE_RUBY_HOST` environment variable +to ensure your Rails application can connect to the correct SSR server. The value **_VITE_RUBY_HOST: "vite_ssr"_** +must match the **_network-alias_** defined in the `vite_ssr` role above. + +```yml +# Inject ENV variables into containers (secrets come from .kamal/secrets). +env: + secret: + - RAILS_MASTER_KEY + clear: + # 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 + + VITE_RUBY_HOST: "vite_ssr" // [!code ++] + + # 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 inertia_rails_svelte5_ssr-db for a db accessory server on same machine via local kamal docker network. + DB_HOST: 192.168.0.2 + + # Log everything from Rails + # RAILS_LOG_LEVEL: debug + +``` + + +## Update asset_path in`config/deploy.yml` + +Update the asset_path to `/rails/public/vite` if you haven't. + +```yml +# 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 // [!code --] +asset_path: /rails/public/vite // [!code ++] +``` + + +## Ensure that your `vite.config.ts` is configured to support SSR + +Configure Vite with an `ssr` block in your `vite.config.ts` file to ensures all dependencies are bundled for SSR. + +```js +import { svelte } from '@sveltejs/vite-plugin-svelte' +import { defineConfig } from 'vite' +import ViteRails from "vite-plugin-rails" + +export default defineConfig({ + ssr: {// [!code ++] + noExternal: true,// [!code ++] + },// [!code ++] + plugins: [ + svelte(), + ViteRails({ + envVars: { RAILS_ENV: "development" }, + envOptions: { defineOn: "import.meta.env" }, + fullReload: { + additionalPaths: [], + }, + }), + ], +}) +``` + +## Deploy and enjoy πŸŽ‰ + +Once everything is set up, you can deploy your application by running: + +* `kamal setup` (if you haven’t provisioned the server yet). +* `kamal deploy` (to deploy your application). + +In just a few minutes, your application will be live and ready, complete with SSR support! πŸŽ‰ +Good luck, and happy deploying! πŸš€ From 87cbf4c19e18fa6639dcba0e1a506ae278142cc4 Mon Sep 17 00:00:00 2001 From: Brodie Date: Fri, 29 Nov 2024 21:02:12 +1100 Subject: [PATCH 2/8] Update the Dockerfile compared to the fresh default Rails 8 --- docs/cookbook/deploy-with-kamal.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index dd67e72f..d8b2fd76 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -22,8 +22,8 @@ guarantees that the Node.js runtime is available for both the **_build_** stage # check=error=true # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand: -# docker build -t bn_quanlynhatro . -# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name bn_quanlynhatro bn_quanlynhatro +# docker build -t fresh_rails . +# docker run -d -p 80:80 -e RAILS_MASTER_KEY= --name fresh_rails fresh_rails # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html @@ -57,29 +57,22 @@ ENV RAILS_ENV="production" \ # Throw-away build stage to reduce size of final image FROM base AS build -# Install packages needed to build gems and node modules +# Install packages needed to build gems and node modules // [!code ++] +# Install packages needed to build gems // [!code --] RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y build-essential git libpq-dev node-gyp pkg-config python-is-python3 && \ + apt-get install --no-install-recommends -y build-essential git libpq-dev node-gyp pkg-config python-is-python3 && \ // [!code ++] + apt-get install --no-install-recommends -y build-essential git pkg-config && \ // [!code --] rm -rf /var/lib/apt/lists /var/cache/apt/archives -# Install JavaScript dependencies // [!code --] -ARG NODE_VERSION=22.11.0 // [!code --] -ARG YARN_VERSION=1.22.22 // [!code --] -ENV PATH=/usr/local/node/bin:$PATH // [!code --] -RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ // [!code --] - /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ // [!code --] - npm install -g yarn@$YARN_VERSION && \ // [!code --] - rm -rf /tmp/node-build-master // [!code --] - # Install application gems COPY .ruby-version Gemfile Gemfile.lock ./ RUN bundle install && \ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \ bundle exec bootsnap precompile --gemfile -# Install node modules -COPY package.json yarn.lock ./ -RUN yarn install --frozen-lockfile +# Install node modules // [!code ++] +COPY package.json yarn.lock ./ // [!code ++] +RUN yarn install --frozen-lockfile // [!code ++] # Copy application code COPY . . @@ -90,7 +83,7 @@ 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 -RUN rm -rf node_modules +RUN rm -rf node_modules // [!code ++] # Final stage for app image From c87abd7f187c4f732c6e6a7c2ed88bc1b82fb709 Mon Sep 17 00:00:00 2001 From: Brodie Date: Fri, 29 Nov 2024 21:07:15 +1100 Subject: [PATCH 3/8] Keep sql3 instead of postgres in the sample Dockerfile to stick with default Rails setup --- docs/cookbook/deploy-with-kamal.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index d8b2fd76..ec590577 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -36,7 +36,7 @@ WORKDIR /rails # Install base packages RUN apt-get update -qq && \ - apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \ + apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \ rm -rf /var/lib/apt/lists /var/cache/apt/archives # Install JavaScript dependencies // [!code ++] @@ -157,7 +157,7 @@ env: # Match this to any external database server to configure Active Record correctly # Use inertia_rails_svelte5_ssr-db for a db accessory server on same machine via local kamal docker network. - DB_HOST: 192.168.0.2 + # DB_HOST: 192.168.0.2 # Log everything from Rails # RAILS_LOG_LEVEL: debug From e170a816629cf9a2bcaad35b5429be7a57e93b5a Mon Sep 17 00:00:00 2001 From: Brodie Date: Fri, 29 Nov 2024 21:31:12 +1100 Subject: [PATCH 4/8] Add missing step to configure SSR URL for Inertia's Rails adapter --- docs/cookbook/deploy-with-kamal.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index ec590577..b5a75b83 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -204,6 +204,24 @@ export default defineConfig({ }) ``` +## Configure SSR URL in the Inertia's Rails adapter + +To enable Server-Side Rendering (SSR) in your Inertia Rails application, you need to specify +the correct SSR server URL in the adapter. By default, the adapter points to `http://localhost:13714`, +but this must align with the **_VITE_RUBY_HOST_** value defined in your `deploy.yml` when we deploy it to production. + +Update the configuration in `config/initializers/inertia_rails.rb` to dynamically construct the +SSR URL using Vite Ruby's protocol, host, and port settings: + +```ruby +InertiaRails.configure do |config| + config.ssr_enabled = ViteRuby.config.ssr_build_enabled + config.ssr_url = "#{ViteRuby.config.protocol}://#{ViteRuby.config.host}:13714" // [!code ++] + config.version = ViteRuby.digest +end +``` + + ## Deploy and enjoy πŸŽ‰ Once everything is set up, you can deploy your application by running: From 6adf9c649f859d3f1c7969a761f7fdc1be2095ec Mon Sep 17 00:00:00 2001 From: Brodie Date: Fri, 29 Nov 2024 21:32:41 +1100 Subject: [PATCH 5/8] Drop code change highlighter as it doesn't take effect on Ruby code block --- docs/cookbook/deploy-with-kamal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index b5a75b83..aebe5379 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -216,7 +216,7 @@ SSR URL using Vite Ruby's protocol, host, and port settings: ```ruby InertiaRails.configure do |config| config.ssr_enabled = ViteRuby.config.ssr_build_enabled - config.ssr_url = "#{ViteRuby.config.protocol}://#{ViteRuby.config.host}:13714" // [!code ++] + config.ssr_url = "#{ViteRuby.config.protocol}://#{ViteRuby.config.host}:13714" config.version = ViteRuby.digest end ``` From 761c8f51ca42421890a13b4fb9ca2bb5edf18509 Mon Sep 17 00:00:00 2001 From: Brodie Date: Fri, 29 Nov 2024 21:52:36 +1100 Subject: [PATCH 6/8] Add the code diff highlighter back in ruby snippets --- docs/cookbook/deploy-with-kamal.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index aebe5379..7e6ef2e4 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -216,7 +216,7 @@ SSR URL using Vite Ruby's protocol, host, and port settings: ```ruby InertiaRails.configure do |config| config.ssr_enabled = ViteRuby.config.ssr_build_enabled - config.ssr_url = "#{ViteRuby.config.protocol}://#{ViteRuby.config.host}:13714" + config.ssr_url = "#{ViteRuby.config.protocol}://#{ViteRuby.config.host}:13714" # [!code ++] config.version = ViteRuby.digest end ``` From 2ee2911fe7f1be98fed0b01bc1d00fb002f93874 Mon Sep 17 00:00:00 2001 From: Brodie Date: Wed, 4 Dec 2024 19:59:01 +1100 Subject: [PATCH 7/8] Update the guide to reflect the recent refactor on how to set SSR URL via ENV variable --- docs/cookbook/deploy-with-kamal.md | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index 7e6ef2e4..844ab62a 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -207,19 +207,10 @@ export default defineConfig({ ## Configure SSR URL in the Inertia's Rails adapter To enable Server-Side Rendering (SSR) in your Inertia Rails application, you need to specify -the correct SSR server URL in the adapter. By default, the adapter points to `http://localhost:13714`, -but this must align with the **_VITE_RUBY_HOST_** value defined in your `deploy.yml` when we deploy it to production. - -Update the configuration in `config/initializers/inertia_rails.rb` to dynamically construct the -SSR URL using Vite Ruby's protocol, host, and port settings: - -```ruby -InertiaRails.configure do |config| - config.ssr_enabled = ViteRuby.config.ssr_build_enabled - config.ssr_url = "#{ViteRuby.config.protocol}://#{ViteRuby.config.host}:13714" # [!code ++] - config.version = ViteRuby.digest -end -``` +the correct SSR server URL in the adapter. It can be set via the `INERTIA_SSR_URL` ENV variable. + +Given we are using kamal 2, I would suggest to leverage [kamal secret](https://kamal-deploy.org/docs/commands/secrets/) +configuration at `.kamal/secrets` ## Deploy and enjoy πŸŽ‰ From 8ea7c9cad975f79ce88914e96cacab18e2391879 Mon Sep 17 00:00:00 2001 From: Brodie Date: Wed, 4 Dec 2024 21:27:34 +1100 Subject: [PATCH 8/8] Drop the suggestion to use kamal secret for Rails env --- docs/cookbook/deploy-with-kamal.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/cookbook/deploy-with-kamal.md b/docs/cookbook/deploy-with-kamal.md index 844ab62a..ff4931b2 100644 --- a/docs/cookbook/deploy-with-kamal.md +++ b/docs/cookbook/deploy-with-kamal.md @@ -209,9 +209,6 @@ export default defineConfig({ To enable Server-Side Rendering (SSR) in your Inertia Rails application, you need to specify the correct SSR server URL in the adapter. It can be set via the `INERTIA_SSR_URL` ENV variable. -Given we are using kamal 2, I would suggest to leverage [kamal secret](https://kamal-deploy.org/docs/commands/secrets/) -configuration at `.kamal/secrets` - ## Deploy and enjoy πŸŽ‰