Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t rails_8_template .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name rails_8_template rails_8_template
# docker run -d -p 80:80 -e SECRET_KEY_BASE=<generated secret> --name rails_8_template rails_8_template

# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html

Expand Down Expand Up @@ -71,4 +71,8 @@ ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS http://localhost/up || exit 1

CMD ["./bin/thrust", "./bin/rails", "server"]
3 changes: 3 additions & 0 deletions app/jobs/application_job.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
class ApplicationJob < ActiveJob::Base
# Keep enqueue semantics portable even though jobs currently share the primary database.
self.enqueue_after_transaction_commit = true

# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked

Expand Down
3 changes: 2 additions & 1 deletion config/cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ default: &default
store_options:
# Cap age of oldest cache entry to fulfill retention policies
# max_age: <%= 60.days.to_i %>
max_size: <%= 256.megabytes %>
# Leave most of Supabase Free's database quota for application data and Solid Queue/Cable.
max_size: <%= Integer(ENV.fetch("SOLID_CACHE_MAX_SIZE_MB", 64)).megabytes %>
namespace: <%= Rails.env %>

development:
Expand Down
1 change: 0 additions & 1 deletion config/credentials.yml.enc

This file was deleted.

24 changes: 13 additions & 11 deletions config/puma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,20 @@
# Allow puma to be restarted by `bin/rails restart` command.
plugin :tmp_restart

# Run the Solid Queue supervisor inside of Puma for single-server deployments
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]
# Run the Solid Queue supervisor inside of Puma for single-server deployments.
if ENV["SOLID_QUEUE_IN_PUMA"] == "true"
plugin :solid_queue

# Run Solid Queue in async mode: worker/dispatcher/scheduler run as threads
# inside the Puma master process instead of being forked into 4 subprocesses.
# Required on Render's 512MB free plan — fork mode adds ~460MB of process
# overhead (supervisor + 3 children) and pushes the container into OOM-loop.
# Async mode collapses that to ~50MB of additional thread overhead in Puma
# master. Trade-off: less isolation between SQ threads and Puma — a hung or
# leaky SQ thread affects request serving. For low-traffic student projects
# the trade-off is right; revisit if upgrading to a paid plan with more RAM.
solid_queue_mode :async if ENV["SOLID_QUEUE_IN_PUMA"]
# Run Solid Queue in async mode: worker/dispatcher/scheduler run as threads
# inside the Puma master process instead of being forked into 4 subprocesses.
# Required on Render's 512MB free plan — fork mode adds ~460MB of process
# overhead (supervisor + 3 children) and pushes the container into OOM-loop.
# Async mode collapses that to ~50MB of additional thread overhead in Puma
# master. Trade-off: less isolation between SQ threads and Puma — a hung or
# leaky SQ thread affects request serving. For low-traffic student projects
# the trade-off is right; revisit if upgrading to a paid plan with more RAM.
solid_queue_mode :async
end

# Specify the PID file. Defaults to tmp/pids/server.pid in development.
# In other environments, only set the PID file if requested.
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
Rails.application.routes.draw do
# Liveness only: 200 when Rails boots successfully, 500 otherwise.
get "up" => "rails/health#show", as: :rails_health_check

# This is a blank app! Pick your first screen, build out the RCAV, and go from there. E.g.:
# get("/your_first_screen", { :controller => "pages", :action => "first" })
end
3 changes: 1 addition & 2 deletions render.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ services:
plan: free # make sure to set this to free or you'll get billed $$$
region: ohio # set this to the SAME region as your database, near your users (Supabase "East US (Ohio)" = ohio) to avoid slow cross-region queries
dockerfilePath: ./Dockerfile
healthCheckPath: /up
envVars: # this section sets some ENV variables needed by Render for deployment
- key: SECRET_KEY_BASE
generateValue: true
Expand All @@ -15,8 +16,6 @@ services:
value: "0" # Puma single mode (no forked workers); greater value causes OOM on 512 MB free tier
- key: RAILS_MAX_THREADS
value: "3" # Cap threads to reduce memory
- key: MALLOC_ARENA_MAX
value: "2" # Reduce memory fragmentation
- key: SOLID_QUEUE_IN_PUMA
value: "true" # Run background jobs in same process
- key: BUNDLE_WITHOUT
Expand Down
7 changes: 7 additions & 0 deletions spec/jobs/application_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "rails_helper"

RSpec.describe ApplicationJob do
it "defers enqueues until the surrounding transaction commits" do
expect(described_class.enqueue_after_transaction_commit).to be(true)
end
end
9 changes: 9 additions & 0 deletions spec/requests/health_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "rails_helper"

RSpec.describe "Health check", type: :request do
it "reports that Rails booted" do
get "/up"

expect(response).to have_http_status(:ok)
end
end