Deploying a Rails PWA with Kamal 2 on Hetzner
Kamal 2: Built-In with Rails 8
Rails 8 includes Kamal 2 out of the box, meaning:
- The app generator provides a
Dockerfile
and kamaldeploy.yml
. - Kamal manages zero-downtime deployments using Docker.
- It automates SSL setup, server configuration, and app updates.
Dockerfile
for Rails 8 PWA (with SQLite)
#syntax=docker/dockerfile:1
#check=error=true
#Base Ruby image (Rails 8 compatible)
ARG RUBY_VERSION=3.3.6
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
#Set working directory
WORKDIR /rails
#Install required system dependencies
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y \
curl libjemalloc2 libvips sqlite3 \
nodejs npm yarn && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
#Set production environment variables
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
#Throw-away build stage to reduce image size
FROM base AS build
#Install dependencies
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 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 application source code
COPY . .
#Install JavaScript dependencies
RUN yarn install --frozen-lockfile
#Precompile assets and bootsnap cache
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
RUN bundle exec bootsnap precompile app/ lib/
#Final runtime image
FROM base
#Copy built artifacts
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
#Set ownership and permissions
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 node_modules public/assets
USER 1000:1000
#Entrypoint prepares the database
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
#Expose only port 80 (handled by Kamal-proxy)
EXPOSE 80
#Start server using Puma
CMD ["./bin/thrust", "./bin/rails", "server"]
1. Initialize Kamal in Your Rails 8 App
If you're starting a new project:
rails new my_pwa
cd my_pwa
For existing projects, initialize Kamal:
kamal init
2. Updating Your kamal deploy.yml Configuration
Modify config/deploy.yml
with your server and registry details:
#Name of your application. Used to uniquely configure containers.
service: my_pwa
#Name of the container image.
image: your-username/my_pwa
#Deploy to these servers.
servers:
web:
- 192.168.0.1
#Enable SSL auto certification via Let's Encrypt.
proxy:
ssl: true
host: app.YOUR-DOMAIN-NAME.com
#Credentials for your image host.
registry:
username: your-username
password:
- KAMAL_REGISTRY_PASSWORD
#Inject ENV variables into containers.
env:
secret:
- RAILS_MASTER_KEY
clear:
SOLID_QUEUE_IN_PUMA: true
#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:
- "my_pwa_storage:/rails/storage"
#Bridge fingerprinted assets (CSS, JS) between versions.
asset_path: /rails/public/assets
#Configure the image builder.
builder:
arch: amd64
#Aliases for common tasks.
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"
3. Running Kamal Setup on the Hetzner VPS
Kamal automates everything, including installing Docker and dependencies. Run:
kamal setup
This will:
- ✅ Install Docker and dependencies on your VPS.
- ✅ Set up SSH access for Kamal.
- ✅ Prepare the VPS for containerized deployments.
4. Deploying Your Rails 8 PWA with Kamal 2
Run the following command to build and deploy:
kamal deploy
Kamal will:
- Build a Docker image of your app.
- Push it to DockerHub or GHCR.
- Pull and run it on your Hetzner VPS.