October 5, 2023·6 min read·engineeringrubyrailsdockerauthentication

Rails 7.1 makes production packaging part of a new application

Generated Dockerfiles establish a production build path, while new authentication helpers make normalization, password lookup, and token invalidation more explicit.

A framework can make application creation easy while leaving production packaging as a separate collection of local conventions. Rails 7.1 brings that packaging into the generated application. A new project receives a Docker build designed around production dependencies, cached installation steps, and a separate final runtime stage.

The October 5 release announcement makes generated Dockerfiles one of Rails 7.1's central changes. It also adds authentication-building helpers and broader asynchronous database queries. These changes address different parts of application maintenance, but each makes an existing responsibility more explicit in the framework.

I find Ruby's syntax and object model approachable for larger applications. Rails is most interesting to me when its conventions explain how the application is assembled, including the code and configuration needed after the first local run.

The Dockerfile distinguishes build work from runtime work

The Rails 7.1.0 Dockerfile template begins with a slim Ruby image and establishes the application's working directory. It sets the production Rails environment and Bundler deployment settings before defining a separate build stage.

That build stage installs packages required to compile gems and, where appropriate, JavaScript dependencies. The final stage begins again from the base image. It copies the installed gems and application from the build stage rather than retaining every tool used to produce them.

The distinction addresses the contents of the shipped image. A compiler needed for a native extension does not necessarily need to remain in the runtime image. Separate stages give the template a place to express that difference.

The template also keeps the Ruby version aligned with the application's declared version. Its comment explicitly relates the image argument to the version in the application files. A generated starting point still needs a consistent runtime selection rather than an unrelated container default.

Copy order gives dependency installation a separate cache boundary

The template copies Gemfile and Gemfile.lock before copying the full application source. It then runs bundle install and removes installation caches and git metadata from relevant bundled dependencies.

That order matters to rebuild behavior. Dependency installation depends on the dependency declarations before it depends on the rest of the application's source tree. A source-only edit can therefore preserve the earlier dependency layer when the build cache is available.

JavaScript installation follows the same general separation when the selected application setup uses Node or Bun. The template contains conditional paths for those choices, so a generated image reflects the project's frontend setup instead of always installing every supported tool.

For applications using Bootsnap, the build stage precompiles relevant code. For applications with an asset pipeline, it also precompiles production assets using a dummy secret-key-base setting. The template explicitly describes this as avoiding a requirement for the production Rails master key during asset precompilation.

That last choice separates building static application artifacts from supplying production secrets. The generated build does not need to assume that every machine creating an image also holds the application's live master key.

The final stage defines how the application starts

The final image installs runtime packages when needed, copies the built application and gems, and creates a rails user. It assigns ownership of runtime directories and runs as that user.

Its entrypoint prepares the database, and its default command starts the Rails server on the exposed application port. These choices make startup behavior reviewable in the generated project. They also show that the Dockerfile is intended for running an application, rather than merely providing a development shell.

The release announcement says the files can support Kamal or another Docker-based deployment arrangement. Generating the image definition does not itself choose the complete hosting system. Networking, persistent data, and the surrounding deployment configuration still belong to the chosen environment.

This is a useful scope for a framework convention. A new application starts with a production-oriented build path whose layers and command can be inspected. That gives later changes a concrete baseline.

Authentication helpers cover specific operations

Rails 7.1 does not present these changes as a complete universal authentication system. The announcement describes tools that complement has_secure_password. Their value lies in making particular operations available with clear behavior.

The normalizes declaration applies a transformation to an attribute. The release example strips surrounding whitespace and lowercases an email value before persistence. That is a declared normalization policy, not an assumption that every application should treat every identifier identically.

The authenticate_by method combines lookup by non-password attributes with password authentication. The 7.1.0 implementation explains why it also performs password digest work when a record is not found.

Without comparable work, a missing account and an existing account with an incorrect password can have different timing characteristics. The helper mitigates timing-based enumeration by avoiding the simple missing-record shortcut for ordinary nonempty password attempts.

The implementation still returns immediately for nil or empty password values and requires both password and finder arguments. Its contract is therefore more precise than a claim that every possible authentication input takes identical time. Successful authentication returns the record, while failure returns nil.

Signed tokens can follow a record's changing state

The generates_token_for API defines tokens with a purpose, optional expiry, and optional record-derived data. The token implementation signs the payload and compares the embedded data with newly evaluated data during lookup.

The release's password-reset example uses a fragment of the password salt. Changing the password changes the value used for comparison, invalidating tokens generated with the previous value. Expiration supplies a separate time-based limit.

class User < ApplicationRecord
  has_secure_password

  generates_token_for :password_reset, expires_in: 15.minutes do
    password_salt&.last(10)
  end
end

The distinction between expiration and state-based invalidation matters. A token can become invalid because its allowed time has passed or because the relevant record data changed. Merely resolving the token does not consume it in this implementation.

That is how the API can support a single-use password-reset workflow without a separate stored token that lookup automatically deletes. The password update supplies the state change that prevents reuse. An application needs to perform that update for the example's invalidation behavior to occur.

The implementation also warns that the block's result is embedded as readable JSON. Signing protects against tampering but does not encrypt the payload. A token definition must therefore choose record-derived data appropriate for exposure rather than embedding arbitrary sensitive attributes.

I prefer this explicit mechanism to a helper described only as secure token generation. The definition states which record state matters, and the source shows exactly when the token stops matching it.

Current-password checks are a separate protection

The release also extends has_secure_password with password_challenge. An application can require the existing password when accepting a password update. The announcement demonstrates a failed update with an empty challenge and a successful one with the correct current password.

This protects a different step from password-reset token lookup. A user changing a password through an authenticated account workflow can provide the current password. A reset flow uses a different proof and then changes the password state that invalidates the token.

The helpers are useful because those policies remain visible. A normalization declaration, a password-aware finder, and a token definition each express a specific responsibility. None removes the need to decide which workflow calls it.

Database concurrency expands beyond relations

Rails 7.1 adds asynchronous aggregate and value-returning queries, including async_count, async_sum, async_pluck, and SQL-based variants. The announcement describes these as extending the existing asynchronous-query support to more result shapes.

This can let independent slow queries proceed in parallel while request handling continues. It is separate from changing the SQL's cost or adding an index. The same database work still has to execute, but the application can overlap work that does not depend on an earlier result.

Other release additions target different database and job patterns. Composite primary keys become supported in Active Record, and perform_all_later can enqueue multiple jobs while avoiding the per-job callbacks described by the announcement. Those are explicit tradeoffs in data modeling and bulk operations.

The production Dockerfile supplies a reproducible starting point for packaging these applications. The authentication and database APIs address behavior within them. Rails 7.1 makes each responsibility more concrete through generated configuration or narrowly defined methods that a maintainer can inspect in the application and framework source.