Sinatra is a lightweight Ruby DSL for creating web applications with minimal effort. Unlike Rails, Sinatra gives you just what you need for web routing and leaves everything else to your choice. This guide covers building and deploying a Sinatra application on a VPS with Puma and Nginx.
Project Setup
# Install Ruby via rbenv
sudo apt install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0
# Create project
mkdir sinatra-app && cd sinatra-app
# Gemfile
source "https://rubygems.org"
gem "sinatra", "~> 4.0"
gem "sinatra-contrib"
gem "puma", "~> 6.4"
gem "sequel", "~> 5.77"
gem "pg"
gem "json"
gem "rack", "~> 3.0"
# Install
bundle install
Application Code
# app.rb
require "sinatra/base"
require "sinatra/json"
require "sequel"
require "json"
DB = Sequel.connect(ENV["DATABASE_URL"] || "postgres://localhost/sinatra_dev")
class App < Sinatra::Base
configure do
set :server, :puma
set :bind, "0.0.0.0"
set :port, ENV.fetch("PORT", 3000)
set :show_exceptions, false
set :logging, true
end
before do
content_type :json
end
get "/health" do
json status: "healthy"
end
get "/api/users" do
users = DB[:users].all
json users
end
get "/api/users/:id" do
user = DB[:users].where(id: params[:id].to_i).first
halt 404, json(error: "Not found") unless user
json user
end
post "/api/users" do
data = JSON.parse(request.body.read)
id = DB[:users].insert(name: data["name"], email: data["email"])
user = DB[:users].where(id: id).first
status 201
json user
end
error do
status 500
json error: "Internal server error"
end
end
Puma Configuration
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY", 2)
threads_count = ENV.fetch("MAX_THREADS", 5)
threads threads_count, threads_count
port ENV.fetch("PORT", 3000)
environment ENV.fetch("RACK_ENV", "production")
preload_app!
on_worker_boot do
Sequel.connect(ENV["DATABASE_URL"])
end
Rack Configuration
# config.ru
require "./app"
run App
Deployment
# On VPS
cd /opt/sinatra-app
bundle install --deployment --without development test
# Systemd service
# /etc/systemd/system/sinatra-app.service
[Unit]
Description=Sinatra Application
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/sinatra-app
ExecStart=/usr/local/bin/bundle exec puma -C config/puma.rb
EnvironmentFile=/opt/sinatra-app/.env
Restart=always
[Install]
WantedBy=multi-user.target
Docker Deployment
FROM ruby:3.3-alpine
RUN apk add --no-cache build-base postgresql-dev
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install --jobs 4 --retry 3 --without development test
COPY . .
USER nobody
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
Summary
Sinatra is perfect for microservices, APIs, and small web applications where Rails would be overkill. With Puma for multi-threaded request handling and Sequel for database access, you get a production-ready Ruby stack in a fraction of Rails complexity. The deployment is straightforward: bundle install, configure Puma, run behind Nginx, and manage with systemd.