config/deploy.yml
# Name of your application. Used to uniquely configure containers.
service: <SERVICE_NAME>
# Name of the container image.
image: <DOCKER_USERNAME>/<IMAGE_NAME>
# Deploy to these servers.
servers:
web:
hosts:
- <SERVER_IP_ADDRESS>
labels:
traefik.http.routers.<DOMAIN>.entrypoints: websecure
traefik.http.routers.<DOMAIN>.rule: 'Host(`<DOMAIN_NAME>`) || Host(`<DOMAIN_NAME_WITH_WWW>`)'
traefik.http.routers.<DOMAIN>.tls.certresolver: letsencrypt
options:
network: 'private'
# Credentials for your image host.
registry:
username: <DOCKER_USERNAME>
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .env).
# Remember to run `kamal env push` after making changes!
env:
clear:
HOSTNAME: <DOMAIN_NAME> DB_HOST: <SERVER_IP_ADDRESS>
POSTGRES_USER: <APP_NAME>_user
POSTGRES_DB: <APP_NAME>_production
RAILS_SERVE_STATIC_FILES: true
RAILS_LOG_TO_STDOUT: true
secret:
- RAILS_MASTER_KEY - POSTGRES_PASSWORD
# Use a different ssh user than root
# ssh:
# user: app
volumes:
- '/storage:/rails/storage'
accessories:
db:
image: postgres:16
host: <SERVER_IP_ADDRESS>
port: 5432
env:
clear:
DB_HOST: <SERVER_IP_ADDRESS>
POSTGRES_USER: <APP_NAME>_user
POSTGRES_DB: <APP_NAME>_production
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
# Configure custom arguments for Traefik
traefik:
options:
publish:
- '443:443'
volume:
- '/letsencrypt/acme.json:/letsencrypt/acme.json'
network: 'private'
args:
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: '<YOUR_EMAIL_ADDRESS>'
certificatesResolvers.letsencrypt.acme.storage: '/letsencrypt/acme.json'
certificatesResolvers.letsencrypt.acme.httpchallenge: true
certificatesResolvers.letsencrypt.acme.httpchallenge.entrypoint: web
config/database.yml
# SQLite. Versions 3.8.0 and up are supported.
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem "sqlite3"
#
default: &default
adapter: sqlite3
pool: 5
timeout: 5000
development:
<<: *default
database: storage/<APP_NAME>_development.sqlite3
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: storage/<APP_NAME>_test.sqlite3
production:
<<: *default
database: storage/<APP_NAME>_production.sqlite3
# PostgreSQL. Versions 9.3 and up are supported.
#
# Install the pg driver:
# gem install pg
# On macOS with Homebrew:
# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
# On Windows:
# gem install pg
# Choose the win32 build.
# Install PostgreSQL and put its /bin directory on your path.
#
# Configure Using Gemfile
# gem "pg"
#
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: <APP_NAME>_development
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: <APP_NAME>_test
production:
<<: *default
host: '<%= ENV["DB_HOST"] %>'
database: '<%= ENV["POSTGRES_DB"] %>'
username: '<%= ENV["POSTGRES_USER"] %>'
password: '<%= ENV["POSTGRES_PASSWORD"] %>'
Dockerfile
# syntax = docker/dockerfile:1
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base
# Rails app lives here
WORKDIR /rails
# 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
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libvips pkg-config
# Install application 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 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
# Final stage for app image
FROM base
# Install packages needed for deployment
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libsqlite3-0 libvips && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Copy built artifacts: gems, application
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER rails:rails
# 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"]
.env
KAMAL_REGISTRY_PASSWORD=dckr_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxx
RAILS_MASTER_KEY=0728xxxxxxxxxxxxxxxxxxxxxxxxc4efPOSTGRES_PASSWORD=passxxxxxxxxxxxxxxxxxxxxxxxxword