Deployment guide

Ruby 3

Overview

Ruby on Rails is the convention-over-configuration web framework that pioneered modern MVC web development. It provides the ActiveRecord ORM, ActionView templates, ActionCable websockets, ActiveJob background queues and scaffolded CRUD generators.

This SecureAnyCloud image installs Ruby 3.4.1 (source-compiled via ruby-build into /opt/ruby) and Rails 8.0.5, with gems vendored under /opt/rails/sac/vendor/bundle. Puma 6 fronts the app on a Unix socket, nginx reverse-proxies on TCP :80 and serves compiled assets directly, and PostgreSQL 16 is the production database. A SecureAnyCloud starter project at /opt/rails/sac includes a scaffolded Article model with a full CRUD UI at /articles and an Admin model seeded at first boot.

What you get:

  • Ruby 3.4.1 (BSD 2-Clause + custom Ruby License) source-compiled at /opt/ruby, with /usr/local/bin/{ruby,gem,bundle} symlinks
  • Rails 8.0.5 (MIT) with gems vendored under /opt/rails/sac/vendor/bundle
  • Puma 6 binding to /run/puma/sac.sock (2 workers, 2-8 threads by default)
  • nginx reverse proxy on TCP :80 (proxy_pass to the puma socket, serves /assets/ from public/assets/)
  • PostgreSQL 16 from Ubuntu noble main, with a per-VM SecureAnyCloud role and a sac_production database
  • SecureAnyCloud starter project at /opt/rails/sac with a scaffolded Article resource (full CRUD at /articles)
  • Admin model with has_secure_password (bcrypt) seeded with email: SecureAnyCloud
  • Per-VM SECRET_KEY_BASE (64 hex), DB password (32 hex) and Admin password (32 hex), rotated at first boot
  • systemd units puma.service, rails-firstboot.service, nginx.service and postgresql.service
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key, and a VNet with a subnet.

Standard_B2s (4 GB RAM) is comfortable for a 2-worker Puma and PostgreSQL setup.

Configure the NSG inbound rules to allow 22/tcp from your management CIDR and 80/tcp from any client CIDR that needs the Rails UI.

Step 1-3: Deploy + SSH (standard pattern)

ssh azureuser@<vm-ip>
4

Service Status + Versions

sudo systemctl is-active puma.service nginx.service postgresql.service /opt/ruby/bin/ruby --version cd /opt/rails/sac && sudo -u rails /opt/ruby/bin/bundle exec bin/rails --version
5

Read Per-VM Credentials

sudo cat /stage/scripts/rails-credentials.log

The file lists RAILS_URL, RAILS_ARTICLES_URL, RAILS_ADMIN_USER, RAILS_ADMIN_EMAIL and RAILS_ADMIN_PASSWORD. It also includes the per-VM RAILS_DB_NAME, RAILS_DB_USER and RAILS_DB_PASSWORD. The file is mode 0600 and readable only by root.

6

HTTP Health + Static Files

curl -sf -o /dev/null -w 'root: HTTP %{http_code}\n' http://127.0.0.1/
curl -sf -o /dev/null -w '/articles: HTTP %{http_code}\n' http://127.0.0.1/articles
ASSET=$(sudo find /opt/rails/sac/public/assets -type f \( -name '*.css' -o -name '*.js' \) 2>/dev/null | head -1) if [ -n "$ASSET" ]; then
curl -sf -o /dev/null -w "asset: HTTP %{http_code}\n" "http://127.0.0.1/assets/$(basename "$ASSET")"; fi

The first two calls reach Puma through nginx. The third call confirms that nginx serves precompiled assets directly from public/assets/, which is faster than going through Rails.

7

Browse the Rails Articles Index

Browse to http://<vm-ip>/. The root path routes to articles#index. On a fresh boot, the list is empty.

8

Create an Article

Select New article, fill in the title and body fields, and submit the form.

9

View the Created Article

After you submit the form, the scaffold redirects to the article detail page, where you can edit or destroy the article.

10

Run Rails Console / Management Commands

set -a; . /etc/rails/sac.env; set +a cd /opt/rails/sac
sudo -E -u rails /opt/ruby/bin/bundle exec bin/rails runner 'puts "article-count:" + Article.count.to_s'
sudo -E -u rails /opt/ruby/bin/bundle exec bin/rails runner 'puts "admin-count:" + Admin.count.to_s'

For an interactive console, use sudo -E -u rails /opt/ruby/bin/bundle exec bin/rails console. bin/rails db:migrate is idempotent, so already-applied migrations are no-ops.

11

Add a Rails Generator (model/scaffold)

Replace <Resource> and the field list with your own:

cd /opt/rails/sac
sudo -u rails /opt/ruby/bin/bundle exec bin/rails generate scaffold Comment body:text article:references
sudo -u rails /opt/ruby/bin/bundle exec bin/rails db:migrate
sudo -u rails /opt/ruby/bin/bundle exec bin/rails assets:precompile
sudo systemctl restart puma.service

The scaffold generates routes, a controller, views and a migration. After db:migrate and assets:precompile, restart Puma so that it loads the new code.

12

Tune Puma Workers

Edit /etc/rails/sac.env to raise PUMA_WORKERS, then restart:

sudo systemctl restart puma.service

The defaults on Standard_B2s are:

  • PUMA_WORKERS: 2
  • PUMA_THREADS_MIN: 2
  • PUMA_THREADS_MAX: 8
  • RAILS_ENV: production
  • RAILS_LOG_TO_STDOUT: true
  • RAILS_SERVE_STATIC_FILES: true
  • DB: PostgreSQL 16, role SecureAnyCloud, db sac_production

For production, raise the worker count to 2 × CPU + 1 (for example, 9 on a D4s_v5).

13

Add Your Domain to Allowed Hosts

Rails 8 ships with Rails.application.config.hosts empty by default in production, so host authorization is not enforced (unlike Django's strict ALLOWED_HOSTS). If you enable host authorization, add your domain in config/environments/production.rb:

Rails.application.config.hosts << "apps.example.com"

Then restart Puma:

sudo systemctl restart puma.service

Most setups behind nginx need no change, because the proxy passes the original Host header through and Rails accepts it.

14

Install Let's Encrypt SSL

Point your domain's DNS A record at this VM, then run Certbot:

sudo apt-get install -y python3-certbot-nginx
sudo certbot --nginx -d apps.example.com -m you@example.com --agree-tos --non-interactive --redirect

Certbot edits /etc/nginx/sites-available/rails-sac to add the SSL server block and a :80 → :443 redirect, then reloads nginx. The certbot.timer systemd unit handles renewal and runs twice daily.

15

Backups

To take a consistent backup while the app is running, dump the database with pg_dump and archive the ActiveStorage storage/ directory:

PASS=$(sudo grep '^RAILS_DB_PASSWORD=' /stage/scripts/rails-credentials.log | cut -d= -f2-) PGPASSWORD="$PASS" pg_dump -h 127.0.0.1 -U sac sac_production | gzip > /var/backups/sac-db-$(date +%F).sql.gz
sudo tar czf /var/backups/sac-storage-$(date +%F).tgz -C /opt/rails/sac storage

Copy /var/backups to Azure Blob Storage (az storage blob upload-batch) on a regular schedule for off-VM retention.

16

Logs and Troubleshooting

sudo journalctl -u puma.service --no-pager -n 80
sudo journalctl -u nginx.service --no-pager -n 30
sudo journalctl -u postgresql.service --no-pager -n 30
sudo tail -50 /var/log/nginx/access.log
sudo tail -50 /var/log/nginx/error.log

RAILS_LOG_TO_STDOUT=true sends Rails logs to the systemd journal through Puma's stdout. For more verbose output while debugging, set RAILS_ENV=development in /etc/rails/sac.env and restart Puma. Never do this in production, because it disables view caching, exposes stack traces and reloads classes on every request.

Security

  • RAILS_ENV=production by default, so the image is production-safe out of the box (no debug stack traces, view caching on).
  • Per-VM SECRET_KEY_BASE (64 hex chars from openssl rand).
  • Puma binds to a Unix socket and is not exposed to the network.
  • PostgreSQL listens on 127.0.0.1:5432 (loopback only).
  • Per-VM DB password (32 hex chars).
  • Per-VM Admin password (32 hex chars), bcrypt-hashed through has_secure_password.
  • The credentials log at /stage/scripts/rails-credentials.log is mode 0600 (root-only).
  • Restrict NSG inbound on :80 to the IP ranges that need the Rails app.
  • Install python3-certbot-nginx for Let's Encrypt automation (Step 14).

Support

SecureAnyCloud provides 24/7/365 expert technical support, with a guaranteed response within 24 hours and a one hour average for critical issues. Contact sales@secureanycloud.com.

Deploy on Azure

Launch Ruby 3.4 with Rails 8 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure