Una aplicación Ruby on Rails en producción se ejecuta con Puma, el servidor de aplicaciones que Rails incluye por defecto, y con Nginx delante para servir los archivos estáticos, terminar TLS y reenviar el resto de peticiones. En este tutorial instalarás Ruby con rbenv en Ubuntu 24.04, prepararás la base de datos en PostgreSQL, ejecutarás Puma como servicio de systemd y publicarás la aplicación con Nginx y un certificado de Let's Encrypt.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM (compilar Ruby consume bastante memoria).
  • Un usuario no root con privilegios sudo. En los ejemplos se llama your_user y es también el usuario que ejecutará la aplicación.
  • Un dominio con un registro DNS A apuntando a la IP del servidor. En los ejemplos se usa your_domain.
  • Una aplicación Rails 7.1 o posterior en un repositorio Git, con los archivos .ruby-version y config/credentials.yml.enc versionados, y su clave maestra config/master.key a mano.

La aplicación de ejemplo se llama myapp y vive en /var/www/myapp.

Paso 1: Instalar las dependencias del sistema

Instala las librerías necesarias para compilar Ruby y las gemas nativas más comunes, además de PostgreSQL y Nginx:

sudo apt update
sudo apt install git curl build-essential autoconf libssl-dev libyaml-dev libreadline-dev zlib1g-dev libgmp-dev libffi-dev libpq-dev postgresql nginx

Abre en el cortafuegos SSH, HTTP y HTTPS:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Paso 2: Instalar rbenv y ruby-build

rbenv permite instalar la versión exacta de Ruby que pide cada aplicación, sin depender de la que trae Ubuntu. Clónalo junto con el plugin ruby-build, que es el que compila Ruby:

git clone https://github.com/rbenv/rbenv.git ~/.rbenv
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

Añade rbenv a tu shell y recarga la configuración:

echo 'eval "$(~/.rbenv/bin/rbenv init - bash)"' >> ~/.bashrc
source ~/.bashrc

Comprueba que el comando está disponible:

rbenv --version
rbenv 1.3.x

Paso 3: Descargar la aplicación e instalar Ruby

Crea el directorio de la aplicación con tu usuario como propietario y clona el repositorio:

sudo mkdir -p /var/www/myapp
sudo chown your_user:your_user /var/www/myapp
git clone https://github.com/your_account/myapp.git /var/www/myapp
cd /var/www/myapp

Ejecutado sin argumentos dentro del proyecto, rbenv install lee la versión de .ruby-version y la compila. Tarda varios minutos:

cat .ruby-version
rbenv install

Comprueba que dentro del proyecto se usa esa versión:

ruby -v
ruby 3.4.x (...) +PRISM [x86_64-linux]

Paso 4: Instalar las gemas de producción

Configura Bundler para que instale las gemas dentro del proyecto y omita los grupos de desarrollo y test. Estos ajustes se guardan en .bundle/config y se aplican a todas las ejecuciones posteriores:

bundle config set --local deployment true
bundle config set --local without 'development test'
bundle install
...
Bundle complete! 18 Gemfile dependencies, 92 gems now installed.
Gems in the groups 'development' and 'test' were not installed.
Bundled gems are installed into `./vendor/bundle`

Paso 5: Crear el usuario de PostgreSQL

Abre config/database.yml y busca la sección production. En las aplicaciones generadas con --database=postgresql, el usuario suele llamarse como la aplicación y la contraseña se lee de una variable de entorno:

production:
  primary: &primary_production
    <<: *default
    database: myapp_production
    username: myapp
    password: <%= ENV["MYAPP_DATABASE_PASSWORD"] %>

Rails 8 define además bases de datos para cache, queue y cable con el mismo usuario. Crea ese usuario con permiso CREATEDB para que Rails pueda crearlas todas. Sustituye your_strong_password por una contraseña larga y aleatoria:

sudo -u postgres psql -c "CREATE USER myapp WITH CREATEDB PASSWORD 'your_strong_password';"

Paso 6: Definir las variables de entorno

Rails necesita en producción el entorno, la contraseña de la base de datos y la clave maestra que descifra config/credentials.yml.enc. Guárdalas en un archivo que usarán tanto systemd como tus comandos:

sudo nano /etc/myapp.env
RAILS_ENV=production
RAILS_MASTER_KEY=contenido_de_config_master_key
MYAPP_DATABASE_PASSWORD=your_strong_password
SOLID_QUEUE_IN_PUMA=1

SOLID_QUEUE_IN_PUMA=1 hace que el config/puma.rb de Rails 8 arranque el procesador de trabajos de Solid Queue dentro de Puma, suficiente en un único servidor. Omítela si tu aplicación no usa Solid Queue.

Protege el archivo, ya que contiene la clave maestra:

sudo chown root:your_user /etc/myapp.env
sudo chmod 640 /etc/myapp.env

Carga las variables en tu sesión actual:

set -a; source /etc/myapp.env; set +a

Paso 7: Preparar la base de datos y compilar los assets

db:prepare crea las bases de datos que falten, carga el esquema y ejecuta las migraciones pendientes:

cd /var/www/myapp
bin/rails db:prepare

Compila los assets en public/assets, desde donde Nginx los servirá directamente:

bin/rails assets:precompile

Comprueba que la aplicación arranca en modo producción y se conecta a la base de datos:

bin/rails runner 'puts ActiveRecord::Base.connection.current_database'
myapp_production

Paso 8: Ejecutar Puma con systemd

Crea un servicio de systemd para Puma. El shim de rbenv usa la versión de .ruby-version, y -b hace que Puma escuche solo en 127.0.0.1:3000, de modo que únicamente Nginx puede llegar a él:

sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=Puma for myapp
After=network.target postgresql.service

[Service]
Type=simple
User=your_user
WorkingDirectory=/var/www/myapp
EnvironmentFile=/etc/myapp.env
ExecStart=/home/your_user/.rbenv/shims/bundle exec puma -C config/puma.rb -b tcp://127.0.0.1:3000
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Activa e inicia el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp --no-pager
● myapp.service - Puma for myapp
     Active: active (running) since ...

Comprueba que Puma responde. La cabecera X-Forwarded-Proto simula lo que enviará Nginx; sin ella, config.force_ssl respondería con una redirección:

curl -s -o /dev/null -w "%{http_code}\n" -H "Host: your_domain" -H "X-Forwarded-Proto: https" http://127.0.0.1:3000/up
200

/up es la ruta de comprobación de salud que Rails añade desde la versión 7.1. Los logs de la aplicación están en journalctl -u myapp, porque Rails escribe en la salida estándar en producción.

Paso 9: Configurar Nginx

Crea un bloque de servidor para el dominio:

sudo nano /etc/nginx/sites-available/myapp

Nginx busca primero el archivo en public/ y, si no existe, pasa la petición a Puma. Los assets compilados llevan un hash en el nombre, así que pueden cachearse durante un año:

upstream puma_myapp {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    listen [::]:80;
    server_name your_domain www.your_domain;
    root /var/www/myapp/public;

    client_max_body_size 20M;

    location ^~ /assets/ {
        add_header Cache-Control "public, max-age=31536000, immutable";
        try_files $uri =404;
    }

    location / {
        try_files $uri @puma;
    }

    location @puma {
        proxy_pass http://puma_myapp;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
}

Activa el sitio, desactiva el sitio por defecto y comprueba la sintaxis:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Nginx necesita poder leer /var/www/myapp/public. Con los permisos por defecto (755 en directorios y 644 en archivos) no hay que cambiar nada.

Paso 10: Activar HTTPS con Let's Encrypt

Instala Certbot con su plugin para Nginx y solicita el certificado. Certbot añade la configuración TLS y la redirección de HTTP a HTTPS:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain -d www.your_domain

Comprueba que la renovación automática funciona:

sudo certbot renew --dry-run

Abre https://your_domain en el navegador. Deberías ver tu aplicación, y https://your_domain/up debe mostrar una página verde.

Actualizar la aplicación

Para desplegar cambios, descarga el código, instala las gemas nuevas, migra, recompila los assets y reinicia Puma:

cd /var/www/myapp
git pull
set -a; source /etc/myapp.env; set +a
bundle install
bin/rails db:migrate
bin/rails assets:precompile
sudo systemctl restart myapp

Si el nuevo código cambia .ruby-version, ejecuta rbenv install antes de bundle install.

Solución de problemas

  • ActiveSupport::MessageEncryptor::InvalidMessage o Missing secret_key_base: RAILS_MASTER_KEY no coincide con config/credentials.yml.enc. Copia de nuevo el contenido de tu config/master.key local.
  • 502 Bad Gateway: Puma no está escuchando. Revisa sudo journalctl -u myapp -n 50.
  • PG::ConnectionBad: password authentication failed: la contraseña de /etc/myapp.env no coincide con la del usuario de PostgreSQL, o el nombre de la variable no es el que usa config/database.yml.
  • Bucle de redirecciones a HTTPS: Nginx no está enviando X-Forwarded-Proto. Comprueba que el bloque @puma incluye esa cabecera.

Conclusión

Tu aplicación Rails funciona en producción con Ruby instalado mediante rbenv, PostgreSQL, Puma gestionado por systemd y Nginx sirviendo los assets y el tráfico HTTPS. Como siguientes pasos puedes:

  • Programar copias de seguridad de las bases de datos con pg_dump.
  • Mover el procesamiento de trabajos a un servicio de systemd propio (bin/jobs) si la carga crece.
  • Automatizar el despliegue con Kamal o con un pipeline de CI.