Deploying Ruby on Rails Applications: Production, Docker, Kamal, and Supporting Services

1. What Deployment Means

Deployment is the process of making an application available in an environment where its intended users can access it. During development, a Rails application usually runs on a developer’s computer. Deployment moves it to an internet-accessible server so that users can reach it through a public address, usually a domain name.

An application is more than its source code. It may depend on Ruby, Ruby gems, JavaScript tools, a database such as PostgreSQL, credentials, and file storage. These components may be installed together on a development computer, but they do not necessarily live on the same machine in production. The application server, database, file storage, and email provider can be separate services. Deploying an application therefore involves configuring its runtime and supporting infrastructure, not simply copying its code.

1.1 Localhost and public access

During local development, a Rails application commonly runs at:

http://localhost:3000

localhost refers to the computer making the request, and port 3000 is where the local Rails server is listening. If someone else enters localhost:3000 in their browser, they connect to their own computer—not the developer’s. To make the application available to other people, it must run on an internet-accessible server and be associated with a public address.

A working production site may also require network configuration, a database, persistent storage, external services, and a certificate for secure connections.

1.2 Servers and cloud computing

A server is a computer that provides services to other computers over a network. A web server runs an application and responds to requests from users’ browsers.

The cloud is a way of providing computing resources through data centers. Cloud services still rely on physical computers, storage, and networking equipment; providers make those resources available remotely, often by dividing the capacity of physical machines into virtual resources.

2. Hosting Models: On-Premises, IaaS, and PaaS

The main hosting models differ in how much infrastructure the application team controls and how much it must configure and maintain.

2.1 On-premises hosting

With on-premises hosting, an organization owns or physically controls the server. The machine could be in an office or another facility. Even a repurposed computer connected to the internet and configured to serve an application fits this model.

The organization is responsible for maintaining the hardware and installing or managing the software needed to run the application, including the operating system, Ruby, PostgreSQL, and other dependencies. This approach provides direct control but also entails operational work, including responsibility for the server and its environment.

2.2 Infrastructure as a Service (IaaS)

With Infrastructure as a Service (IaaS), a provider rents computing infrastructure, commonly in the form of a virtual machine. A virtual machine behaves like a separate computer, although its resources are allocated from a physical host shared with other virtual machines.

For example, a physical machine with 32 GB of RAM could host several virtual machines, each allocated part of that memory. From the user’s perspective, each virtual machine functions as an independent system.

Examples of virtual-machine services discussed in class include:

  • Amazon EC2
  • Google Compute Engine
  • DigitalOcean Droplets
  • Hetzner virtual servers

The provider supplies the virtual machine and underlying infrastructure, but the application team configures much of the software environment. This can include the operating system, Ruby, PostgreSQL, and other dependencies. Administrators commonly connect to a remote server using SSH, typically from a terminal.

IaaS offers more control than a managed application platform, but the team takes on more setup and maintenance.

2.3 Platform as a Service (PaaS)

With Platform as a Service (PaaS), the provider manages more of the environment needed to run an application. Instead of directly administering a virtual machine, developers supply application code and configure how it should be built and run. The platform handles more of the underlying infrastructure and deployment process.

Heroku, Railway, Vercel, Render, and Fly.io were mentioned as platform examples. A common workflow connects an application to a Git repository. When changes are pushed, the platform can build and deploy the updated version. The exact integrations and configuration depend on the provider and application.

PaaS can make an initial deployment simpler. Some platforms recognize common frameworks automatically, while less conventional configurations may need a file describing how to build or run the application. This convenience can involve trade-offs: costs may rise as the application requires additional capacity, such as more memory or a larger database. Vercel was described as being particularly associated with Next.js applications.

2.4 Choosing a hosting model

The choice depends on the team’s operational capacity, desired control, application requirements, and costs. On-premises hosting means operating physical infrastructure directly; IaaS means renting infrastructure and managing much of its software environment; PaaS delegates more of that work to a provider.

The instructor recommended IaaS with DigitalOcean for course deployments, citing familiarity with the provider and a student invitation offer described in class: 60 days of service and a $200 credit. For personal projects, the instructor suggested considering Hetzner as a lower-cost alternative to DigitalOcean. These were the instructor’s recommendations and experience, not a universal price comparison.

A virtual machine can be a middle ground: it requires more administration than PaaS but offers more direct control. Whether IaaS is cheaper than PaaS depends on provider pricing, resource requirements, and operational effort.

3. Development and Production Environments in Rails

A development environment is used while building and testing an application, often on a developer’s own computer. A production environment serves the application to real users. Rails separates environment-specific configuration, commonly in files under config/environments/.

A typical application has configurations for development, testing, and production:

config/
├── environments/
│ ├── development.rb
│ ├── test.rb
│ └── production.rb
└── database.yml

Development prioritizes rapid feedback and diagnostic detail. Production settings are intended to support reliable service, performance, and safer handling of information.

3.1 Code loading and reloading

In development, Rails can detect changes to application files and reload relevant code, allowing many changes to take effect without manually restarting the server. This behavior is often called code reloading or, informally, hot reloading.

Production generally does not reload application code in response to each request. Application processes load code when they start, so new code usually takes effect after a new deployment or process restart. This avoids development-oriented reloading overhead while serving users.

Rails commonly disables eager loading in development to support this workflow and enables it in production. With eager loading, application classes are loaded during application boot rather than on demand. This can improve production execution characteristics and is useful in environments that serve requests concurrently.

3.2 Error reporting

Development error pages are designed to help programmers diagnose problems and can include detailed information such as stack traces.

Production should not expose internal diagnostic details to users. Instead, the application can return a generic error page, such as public/500.html, while detailed information is handled through appropriate operational logging.

A common production setting is:

config.consider_all_requests_local = false

3.3 Front-end assets

Development workflows support rapid editing and feedback on CSS and JavaScript. In production, assets are generally built or prepared as part of a build or deployment rather than recompiled for every request. They may be prepared again for a new application version.

The exact asset pipeline and commands depend on the application’s Rails configuration. The essential distinction is that development favors fast iteration, while production avoids repeated asset-processing work when serving requests.

3.4 Email previews and production delivery

In development, email previews can be used to inspect messages in a browser without sending them to real recipients. Production email delivery is a separate concern: the Rails application must be configured to use an email provider, often through SMTP or another provider-supported integration.

4. Production Configuration and Secrets

Rails production behavior is configured in files such as config/environments/production.rb and config/database.yml. Common production concerns include eager loading, error handling, HTTPS, storage, background jobs, and email.

Production configuration may include settings such as:

config.eager_load = true
config.consider_all_requests_local = false
config.force_ssl = true

A configuration such as config.assume_ssl = true may also be used in setups where a proxy terminates TLS before forwarding requests to the application. The right settings depend on the application and its hosting architecture.

4.1 Database configuration

config/database.yml can contain connection settings for different environments and, in some applications, separate databases for primary data, caching, or job queues. Credentials should not be hardcoded into configuration files. They should be supplied through environment variables or another secure configuration mechanism.

An illustrative production configuration is:

production:
primary:
adapter: postgresql
encoding: unicode
database: my_app_production
username: my_app_user
password: <%= ENV["DATABASE_PASSWORD"] %>
host: <%= ENV.fetch("DATABASE_HOST") { "localhost" } %>
cache:
# Separate cache database configuration, if used
queue:
# Background job database configuration, if used

The database names, environment-variable names, and database layout must match the application’s actual setup.

4.2 Rails encrypted credentials and the master key

Rails can store sensitive configuration, such as API keys, in encrypted credentials. The application needs the corresponding encryption key to decrypt those values at runtime.

The key is commonly stored locally at:

config/master.key

This file should normally be excluded from version control. The encrypted credentials can be present in the codebase, while the key is supplied separately to the deployed application. Rails commonly reads the key from the RAILS_MASTER_KEY environment variable.

The principle is to keep the decryption key out of the repository and provide it securely to the running application.

5. Email Delivery as an External Service

A Rails application does not automatically function as a complete mail-delivery system. It typically delegates email delivery to an external provider through SMTP or another supported interface.

Services such as SendGrid can sit between the application and recipient mail servers. Such services can provide delivery, bounce, open, and link-click metrics, as well as tools for managing deliverability and sender reputation. Direct SMTP delivery is technically possible, but operating a mail server and achieving reliable delivery can require substantial work.

Recipient providers such as Gmail and Outlook use filtering systems to decide whether to accept, reject, or place messages in spam. Delivery is not determined by a simple whitelist: reputation and other factors matter. Using an established transactional email service is a practical way to avoid operating all of that infrastructure directly. The experience of the team behind HEY was mentioned as an example of the effort involved in running mail infrastructure and establishing reliable delivery.

6. Docker and Containerization

6.1 Why package an application in a container?

Before containerized deployment, setting up a server manually could involve installing the correct Ruby version, system build tools and libraries, a JavaScript runtime, gems, and production assets. For example, a server might require build-essential and libpq-dev for native builds and PostgreSQL connectivity, as well as Node.js or related tools for JavaScript assets.

Manual provisioning can cause several problems:

  • Environment differences: A developer’s macOS, Windows, or Linux machine may differ from the production host, creating “it works on my machine” failures.
  • Dependency management: Changes to libraries and system packages can break an existing setup.
  • Scaling effort: Provisioning many servers during a traffic increase can be slow and error-prone.

Docker packages an application and its runtime environment into an image. A running instance of that image is a container. The image can include the application, Ruby and other runtime dependencies, system libraries, gems, and prepared assets.

Containers provide an isolated execution environment and make the packaged application more consistent across hosts. They are not the same as full virtual machines: containers share the host’s operating-system kernel.

6.2 Images, containers, and the build/run lifecycle

The Docker lifecycle has two central stages:

  1. Build: docker build executes the instructions in a Dockerfile, installs dependencies, prepares assets, and produces an image.
  2. Run: docker run creates a running container from that image and starts its configured process, such as the Rails server.

A container can be replaced while the application image remains reproducible. Deployment systems can use the same image to start multiple instances without manually repeating every server setup step.

6.3 Dockerfile structure

Rails-generated applications can include a production-oriented Dockerfile. Its instructions typically define a base image, install system packages, install dependencies, copy the application, prepare assets, and specify how the container starts.

The following is an illustrative structure, not a universal, ready-to-run recipe. Package names and JavaScript tooling must match the application and base image:

# Choose a Ruby base image
FROM ruby:3.3-slim AS base
# Install system dependencies required for building the application
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y \
build-essential \
git \
libpq-dev \
nodejs
WORKDIR /rails
# Copy dependency manifests first to improve build-layer caching
COPY Gemfile Gemfile.lock ./
RUN bundle install
# Copy JavaScript dependency manifests if the application uses them
COPY package.json yarn.lock ./
# Install the JavaScript dependencies using the package manager
# configured for this application.
# Copy the application source
COPY . .
# Prepare production assets
RUN bundle exec rails assets:precompile
# Expose the application port and define its startup command
EXPOSE 80
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
CMD ["./bin/rails", "server", "-b", "0.0.0.0", "-p", "80"]

Copying dependency manifests before the rest of the source can allow Docker to reuse cached dependency layers when application code changes but dependency manifests do not.

A .dockerignore file can exclude files that should not be sent into the build context, such as .git, local logs, temporary files, and local uploads. Build tools and directories that are unnecessary at runtime can also be omitted from a final image. Modern Dockerfiles often achieve this with multi-stage builds: one stage builds dependencies and assets, and a later, smaller stage contains only what is needed to run the application.

7. Kamal: Deploying Containers to a Server

7.1 What Kamal does

PaaS platforms manage much of the infrastructure and deployment runtime. Kamal provides a different approach: it automates deployment of containerized applications to standard virtual servers or bare-metal machines. It was formerly known as MRSK and is included by default in Rails 8 application setups.

Kamal was created by the Rails team but is framework-agnostic: it can deploy applications written in Rails or other languages, provided the application has a valid Dockerfile.

Kamal is an orchestration tool, not a PaaS provider. It helps deploy containers to infrastructure such as a DigitalOcean Droplet, an AWS EC2 instance, or a bare-metal server.

7.2 Deployment lifecycle

A deployment initiated from a developer’s machine commonly follows this sequence:

  1. Kamal builds and tags a production Docker image.
  2. It pushes the image to a container registry, such as Docker Hub.
  3. It connects to the server over SSH.
  4. The server pulls the new image and starts a new application container alongside the existing one.
  5. Kamal checks whether the new application is healthy. In modern Rails applications, the built-in health endpoint is commonly GET /up.
  6. Once the new container passes its health check, the proxy routes incoming traffic to it.
  7. The old container is allowed to finish handling in-flight requests before it is stopped and removed.

This approach supports zero-downtime-style deployments: the intent is to keep the previous version available until the new version is ready. If the new version fails its health check within the configured timeout, deployment can be aborted so the previous version remains in service. The class described a timeout of roughly 30 seconds; actual timing depends on configuration.

7.3 Kamal Proxy, Thruster, and accessories

Kamal Proxy runs as a container on the host and handles incoming web traffic. It can listen on ports 80 and 443, route requests to application containers, and support health checks and HTTPS certificate management.

Thruster can sit in front of the Puma application server inside the web container. It can handle tasks such as caching static assets and offloading some HTTP work, allowing Puma to focus on dynamic Rails requests.

Databases and other dependencies, such as Redis, can be managed as accessories: separate containers with their own configuration. Persistent data can be kept in mounted volumes rather than relying on a disposable container’s filesystem.

Background jobs can run in a separate container built from the same application image. Instead of starting the web server, the job container runs a worker command such as bin/jobs.

7.4 Kamal configuration and commands

Kamal is primarily configured through config/deploy.yml. The following example illustrates the kinds of values it may contain. The IP address, service name, domain, image name, and database details are placeholders and must be adapted to the deployment:

service: loandesk
image: hardware_dm/loandesk
servers:
web:
- 164.90.148.21
job:
hosts:
- 164.90.148.21
cmd: bin/jobs
proxy:
ssl: true
host: loandesk.example.com
registry:
username: hardware_dm
password:
- KAMAL_REGISTRY_PASSWORD
builder:
arch: amd64
accessories:
db:
image: postgres:17
host: 164.90.148.21
port: "5432:5432"
env:
clear:
POSTGRES_DB: loandesk_production
secret:
- POSTGRES_PASSWORD

Sensitive values such as registry tokens, database passwords, and Rails encryption keys should not be committed to source control. They can be supplied through environment variables or another secure mechanism. For example:

export KAMAL_REGISTRY_PASSWORD="your-docker-registry-token"
export LOANDESK_DATABASE_PASSWORD="secure-database-password"

The Rails master key can be read from the local file and supplied to the deployment process without hardcoding its value in the deployment configuration:

RAILS_MASTER_KEY=$(cat config/master.key)

Common Kamal commands include:

Command

Purpose

kamal setup

Performs initial provisioning, such as installing Docker, setting up accessories and Kamal Proxy, and carrying out an initial deployment.

kamal deploy

Builds and pushes the image, deploys it to the server, and coordinates the release process.

kamal app logs -f

Streams application container logs to the local terminal.

kamal app containers

Lists application containers on the remote host.

kamal console

Opens a Rails console in a container on the target host.

kamal rollback <CONTAINER_ID_OR_TAG>

Returns traffic to a previously deployed version, subject to the deployment’s configuration and available images.

8. HTTPS, TLS Certificates, and DNS

8.1 What HTTPS provides

HTTPS uses TLS to protect communication between a browser and a server. TLS encrypts data in transit and allows the browser to validate that the server is authorized to use the requested domain.

For a valid HTTPS connection, the server presents a certificate that the browser can validate, including its domain and validity period. If those checks fail—for example, if a certificate has expired—the browser may warn that the connection is not secure.

A certificate does not prove that a site is honest or safe in every sense. It supports an authenticated, encrypted connection to a domain; it does not certify the site’s content or intentions.

8.2 Let’s Encrypt and Kamal Proxy

Let’s Encrypt is a certificate authority that provides certificates at no cost. Kamal Proxy can obtain and manage certificates for an application.

In the domain-validation process described in class, the requester makes a token available so that Let’s Encrypt can retrieve it and verify that the requester can satisfy the domain-control challenge. Once validation succeeds, Let’s Encrypt issues a certificate for the domain. Kamal Proxy can handle this process for the deployed application.

The certificate example shown in class was issued on February 1, 2026, and expired on March 5, 2027. The instructor described 90 days as a typical certificate lifetime in the workflow being discussed.

8.3 Redirecting HTTP to HTTPS

A certificate makes HTTPS possible, but a site may still receive requests over HTTP. Rails can be configured to redirect those requests to HTTPS:

config.force_ssl = true

This setting complements certificate management: the certificate supports secure connections, while the redirect helps ensure that users reach the secure version of the site. When TLS is terminated at a proxy, the proxy and Rails must be configured consistently.

8.4 DNS

To direct a domain to a Kamal-managed server, a DNS A record can point the domain to the server’s public IP address. For example, loandesk.example.com could point to 164.90.148.21. The proxy on that server can then route requests for the configured domain to the application and, when configured, manage its certificate.

9. Persistent File Storage

Applications often accept user files, such as photographs. If an upload exists only inside an application container, replacing or recreating that container can make the file unavailable.

The application runtime and persistent user data should therefore be treated as separate concerns. Docker volumes can preserve data beyond a container’s lifecycle, and external object storage, such as Amazon S3, can keep uploads independent of the application container and server. Rails Active Storage can be configured to use local storage in development and an external storage service in production.

The production setting that selects an Active Storage backend depends on the application. A common arrangement is local filesystem storage during development and an object-storage service in production.

Separating uploads from replaceable application containers makes it easier to deploy new application versions without making user files depend on a particular container’s filesystem.

10. Database Migrations During Deployments

A rolling deployment can create a period when two versions of the application are active: the old version is still serving requests while the new version starts and database changes are applied. The new application should not receive traffic until it passes its health check, but the old version may still interact with the database during this process.

That overlap makes backward-compatible migrations important. Database changes must not break the currently running version while the new version is being prepared.

For example, adding a NOT NULL column without a suitable default can cause older code to fail when it inserts rows without supplying a value for the new column. Dropping a column too early can break an old application version that still reads or writes it.

A safer general approach is to separate a schema change into stages:

  • Add a new column in a way that remains compatible with the old application, commonly making it nullable initially.
  • Deploy code that can use the new column, and backfill existing data if needed.
  • Apply stricter constraints, such as NOT NULL, in a later deployment once the application and data are ready.
  • For a column that must be removed, first stop application code from using it and deploy that change. Remove the column only after versions that depend on it are no longer active.

Rails models also support ignored_columns, which can help prevent a model from interacting with a column during a transition:

class Loan < ApplicationRecord
self.ignored_columns += ["purpose"]
end

The timing and use of ignored_columns depend on the migration plan. The underlying principle is to avoid changing the schema in a way that makes the old and new application versions incompatible during rollout.

11. Server Capacity

The class recommended at least 2 GB of RAM for the discussed deployment scenario and suggested 4 GB or more for smoother deployments and concurrent container handovers. Asset precompilation, image unpacking, and application boot can require substantial memory. On a 1 GB instance, a deployment may trigger the operating system’s out-of-memory handling.

These figures are practical guidance rather than fixed requirements. Actual resource needs depend on the application, build process, database, and deployment setup.

12. How the Deployment Components Fit Together

A production Rails application depends on several related but distinct pieces:

  • Hosting infrastructure provides the machine or platform where the application runs.
  • Docker packages the application and its runtime dependencies into an image.
  • Kamal coordinates deployment of that image to a server.
  • Kamal Proxy routes incoming web traffic and can manage HTTPS certificates.
  • Rails production configuration controls behavior such as eager loading, error handling, and secure redirects.
  • A database and persistent storage retain application records and user files beyond an individual application container’s lifecycle.
  • An email provider delivers application messages without requiring the application team to operate a complete mail system.

Separating these responsibilities makes deployments more reproducible and helps ensure that application code can be replaced without unintentionally losing data or breaking supporting services.