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 llamayour_usery es también el usuario que ejecutará la aplicación. - Un dominio con un registro DNS
Aapuntando a la IP del servidor. En los ejemplos se usayour_domain. - Una aplicación Rails 7.1 o posterior en un repositorio Git, con los archivos
.ruby-versionyconfig/credentials.yml.encversionados, y su clave maestraconfig/master.keya 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`
Notasi la aplicación usa
jsbundling-railsocssbundling-rails, necesitas además Node.js y el gestor de paquetes que indique el proyecto para compilar los assets. Las aplicaciones con los valores por defecto de Rails (importmap y Propshaft o Sprockets) no lo necesitan.
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::InvalidMessageoMissing secret_key_base:RAILS_MASTER_KEYno coincide conconfig/credentials.yml.enc. Copia de nuevo el contenido de tuconfig/master.keylocal.- 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.envno coincide con la del usuario de PostgreSQL, o el nombre de la variable no es el que usaconfig/database.yml.- Bucle de redirecciones a HTTPS: Nginx no está enviando
X-Forwarded-Proto. Comprueba que el bloque@pumaincluye 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.
