To deploy Rails with PostgreSQL on a VPS, use a supported Ubuntu LTS image, create a non-administrative deploy account, pin the Ruby version with rbenv, install PostgreSQL, create a least-privilege database role, configure Rails through environment-backed settings, and run the app behind a supervised Puma process and Nginx. Put DNS on the server before requesting a TLS certificate, then verify migrations, logs, HTTPS, and restart behavior.
Antihost offers Linux VPS plans with full root access, unlimited traffic on a 1 Gbps port and a 14-day free trial.
You can use Rails’ Kamal workflow, which runs Docker containers and supports zero-downtime deployments, or a traditional host-service stack built from Puma, systemd, Nginx, and Capistrano. The right choice depends on whether you want containerized releases or direct control of services on the VPS.
As an Amazon Associate I earn from qualifying purchases.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Choose the deployment model first
Both supported approaches can serve the same Rails application and PostgreSQL database, but they place operational responsibility in different locations.
| Concern | Kamal | Traditional VPS stack |
|---|---|---|
| Runtime model | Rails runs in Docker containers managed by Kamal. | Ruby, Rails, Puma, PostgreSQL, and Nginx run as host services or processes. |
| Deployment | bin/kamal setup and bin/kamal deploy establish and update the server. |
Capistrano or an equivalent release script updates the application, while systemd supervises Puma. |
| Downtime and rollback | The Rails guide describes zero-downtime container deployment; rollback is tied to the previous image or release. | You own release switching, process restarts, and rollback procedures. A restart can briefly interrupt requests unless you design a phased handoff. |
| Resource overhead | Docker and image storage consume additional disk and memory. | Fewer container layers, but you must maintain the host Ruby and service configuration directly. |
| Configuration ownership | Kamal conventions cover much of the proxy, container, and release wiring. | You configure systemd units, Puma, Nginx, firewall rules, users, and deployment hooks yourself. |
Use Kamal when repeatable container releases and a managed deployment flow matter more than minimizing moving parts. Use the traditional path when you need direct host-level control, already operate Capistrano, or prefer to keep the application process outside Docker.
#1 Best Overall
Provision and harden the Ubuntu VPS
Select the base image and access method
Choose an Ubuntu LTS image supported by your application and add an SSH public key during provisioning. The documented traditional procedure assumes root or sudo access for initial setup. Log in once as the provisioning account, update the package index, and apply security updates before installing application software.
-
Connect with SSH using the key associated with the VPS.
Recommended Free Tools
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Update the operating system packages and reboot if the kernel or core libraries require it.
-
Create a dedicated deploy account rather than running Rails as root. For example, create a user named
deploywith a home directory and no administrative role by default. -
Copy the deployment key or configure the deployment system to authenticate as that user. Keep application files owned by
deploy, not by the web server account.
Restrict network access
Allow only the ports needed for administration and web traffic. SSH is normally required for deployment; HTTP is needed for certificate challenges and redirects; HTTPS serves the application. Do not expose PostgreSQL publicly when Rails and PostgreSQL are on the same VPS. Keep the database listening on localhost unless a separately secured private network is required.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware match- SSH: restrict access to trusted source addresses when your operating environment permits it.
- HTTP: allow port 80 for web traffic and certificate validation.
- HTTPS: allow port 443 for encrypted application traffic.
- PostgreSQL: keep its port inaccessible from the public internet for a single-server deployment.
Confirm that you can open a second SSH session before changing firewall rules. A mistake in the SSH rule can lock out every administrative path.
Install and pin Ruby with rbenv
Do not rely on whichever Ruby version happens to ship with the Ubuntu image. Rails applications often depend on a specific Ruby release recorded in .ruby-version and constrained by the application’s Gemfile.
-
Install the compiler, headers, Git, and other build packages required by rbenv and Ruby.
-
Install rbenv for the deploy user and enable its shell integration in that user’s profile.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsSpecial offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
From the application directory, install the exact version declared by the project:
rbenv install $(cat .ruby-version). -
Select that version for the application with
rbenv local $(cat .ruby-version), then verify it withruby -vandrbenv version. -
Install Bundler at the version required by the lockfile, then run
bundle config set deployment trueso gems are installed from the locked dependency set.
Run these checks as the same user that will execute Puma. A Ruby installed only for root will not be available to the deploy account, and a systemd unit can appear to use the wrong Ruby if its PATH does not include the rbenv shims.
Install PostgreSQL and create a least-privilege database
Install the server and build headers
Install the PostgreSQL server, the contributed extensions package, and libpq-dev. Rails’ PostgreSQL adapter needs the client headers to compile its native extension. After installation, check that the PostgreSQL service is active before creating application credentials.
sudo apt update
sudo apt install postgresql postgresql-contrib libpq-dev
sudo systemctl status postgresql
The distribution package chooses a supported PostgreSQL major version for the Ubuntu release. Record that version for backup, upgrade, and extension planning rather than assuming it will remain unchanged.
Create the role and database
Create a login role dedicated to the Rails application and a database owned by, or explicitly granted to, that role. Do not use the PostgreSQL superuser from Rails.
sudo -u postgres createuser --pwprompt rails_app
sudo -u postgres createdb -O rails_app app_production
Choose a long password and store it in deployment secret storage. Test the credentials locally before Rails starts:
Free tools Windows power users keep installed
One-click scans. No signup required.
psql --host=127.0.0.1 --username=rails_app --dbname=app_production
If this login fails, fix PostgreSQL authentication, role privileges, or the password before debugging Rails. A working local psql connection proves that the server, role, database name, and basic network path agree.
Configure Rails to use PostgreSQL
Use database.yml for stable defaults
Rails’ PostgreSQL adapter uses adapter: postgresql and normally sets encoding: unicode. Keep production values in the environment rather than committing passwords.
production:
adapter: postgresql
encoding: unicode
database: <%= ENV.fetch('POSTGRES_DATABASE', 'app_production') %>
username: <%= ENV.fetch('POSTGRES_USER', 'rails_app') %>
password: <%= ENV.fetch('POSTGRES_PASSWORD') %>
host: <%= ENV.fetch('POSTGRES_HOST', '127.0.0.1') %>
port: <%= ENV.fetch('POSTGRES_PORT', 5432) %>
pool: <%= ENV.fetch('RAILS_MAX_THREADS', 5) %>
The exact pool value must match the concurrency you configure for Puma and the connection capacity of PostgreSQL. Raising it without accounting for every Puma worker can exhaust database connections.
Use DATABASE_URL deliberately
Rails can obtain the complete connection string from DATABASE_URL. When both database.yml and DATABASE_URL are present, Rails merges the settings. Document which values are authoritative so an old host, database name, or pool value in one location does not silently override an intended value in the other.
A typical production URL has this shape:
postgresql://rails_app:[email protected]:5432/app_production
Do not place a real password in a committed file, shell history, issue tracker, or image layer. Inject it at runtime through the deployment mechanism or the service manager’s protected environment.
Account for advisory locks and prepared statements
Rails enables advisory locks by default. If production uses an external pooler such as PgBouncer, evaluate whether session behavior is compatible and consider setting advisory_locks: false when the pooler requires it.
Rails documents up to 1,000 prepared statements per database connection by default. Prepared statements can improve repeated-query performance, but each consumes database memory. On a memory-constrained VPS, lower statement_limit or disable prepared statements after measuring the effect on the application.
Rails retries only safely retryable database queries by default; the documented default is one retry. Set connection_retries explicitly when your failure model requires a different value, and do not treat retries as a substitute for fixing an unavailable database.
Set production environment and secrets
Set RAILS_ENV=production for every release, migration, and application process. Rails uses RAILS_ENV to select production instead of development or test configuration.
At minimum, production needs:
- Rails master key: provide the key needed to decrypt encrypted credentials.
- Database connection: provide
DATABASE_URLor the individual PostgreSQL variables used bydatabase.yml. - Application secrets: supply mail, third-party API, storage, and session-related values required by the app.
- Runtime settings: set hostnames, allowed origins, and any feature flags expected by production configuration.
Keep secrets in the deployment secret store, a protected environment file outside the release repository, or an equivalent mechanism with restricted permissions. Rotate them without committing replacements to Git. Ensure logs never print complete connection URLs or decrypted credentials.
Build and deploy the Rails application
Prepare the release
-
Check out the intended commit into a release directory owned by
deploy.The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Install the locked gems in deployment mode with
bundle install. -
Compile JavaScript, CSS, and other assets when the application’s production build requires it.
-
Load the production environment and run
bin/rails db:prepare, or run the project’s migration task if its release process requires a narrower command. -
Verify that the migration completed before switching traffic to the new release.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
db:prepare creates the database when needed and applies pending migrations in a standard Rails workflow. Review destructive or long-running migrations separately; a migration that locks a large table can still interrupt traffic even when the process supervisor is configured correctly.
Deploy with Kamal
Kamal uses Docker containers to run the application and deploy with zero downtime. The official Rails workflow requires an Ubuntu LTS server with at least 1 GB of RAM, a Docker Hub account and token, and DNS pointed at the server before enabling Let’s Encrypt in the proxy configuration.
-
Configure the server address, registry credentials, application name, environment secrets, and proxy settings in the Kamal configuration.
-
Run
bin/kamal setupfor the initial host and container setup.Recommended: PC Feels Slow? A Free Scan Shows What's Dragging Windows Down →Recommended: Crashes or Glitches? A Free Driver Scan Usually Finds the Culprit →Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Run
bin/kamal deployfor subsequent releases. -
Inspect the container and application logs after each release, then exercise a database-backed request before declaring the deployment complete.
Container images make the Ruby and system dependencies part of the release artifact, which reduces drift between deployments. They also add image storage and Docker maintenance to the VPS workload.
Deploy with Capistrano and systemd
In the traditional path, Capistrano (or an equivalent script) creates timestamped releases, installs gems, runs assets and migrations, and updates a stable current symlink. A systemd unit then runs Puma from that path.
A minimal unit illustrates the required relationships; adapt paths, the Ruby version, and the service account to your application:
[Unit]
Description=Rails Puma application
After=network.target postgresql.service
[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/app/current
Environment=RAILS_ENV=production
EnvironmentFile=/etc/app/production.env
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
After installing the unit, reload systemd, enable the service at boot, start it, and inspect its status and journal. Keep the environment file readable only by root and the service account.
Put Puma behind Nginx
Puma should listen on a loopback TCP port or a Unix socket; Nginx accepts public HTTP connections and forwards them to Puma. This keeps the application process off the public interface and gives you one place to handle TLS, request limits, and redirects.
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
Replace the hostname and upstream port with the values used by your Puma configuration. Validate the Nginx configuration before reloading it. If Nginx returns a gateway error, check that Puma is listening on the same address and port and that systemd has not restarted it repeatedly.
Best Value
Point DNS and enable HTTPS
Create an A or equivalent DNS record that resolves the application hostname to the VPS public address. Wait until the hostname resolves correctly from outside the server before requesting a certificate.
Free tools Windows power users keep installed
One-click scans. No signup required.
With Kamal, enabling SSL in the proxy settings allows it to use Let’s Encrypt after the DNS record points to the server. The HTTP challenge cannot succeed if the hostname still resolves elsewhere or if port 80 is blocked. For a traditional Nginx deployment, use your chosen certificate automation process, configure the certificate and private key in the Nginx server block, redirect HTTP to HTTPS, and reload Nginx.
- Confirm the certificate covers every hostname users will visit.
- Keep renewal automation enabled and test that renewal can reload the proxy.
- Set Rails’ trusted proxy and force-SSL settings consistently with the
X-Forwarded-Protoheader.
Verify the deployment before launch
Run checks from the server and from an external network. A green process status alone does not prove that Rails can authenticate to PostgreSQL or that Nginx is forwarding the correct headers.
-
Check PostgreSQL:
systemctl status postgresql. -
Check the application supervisor:
systemctl status pumafor the traditional path, or inspect the Kamal container status. -
Check Nginx:
systemctl status nginx, then validate its configuration before any reload.DriversCrashes, No Sound, or Screen Glitches?PerformanceWindows Errors? Fix Them Before They SpreadDriversOutdated Drivers Are Slowing You DownSpecial offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Confirm the Rails database login with
psqlusing the same host, role, database, and password supplied to Rails. -
Confirm migrations and schema state with the production Rails command appropriate to your release process.
-
Request the app locally through Puma, then request it publicly through Nginx over HTTP and HTTPS.
-
Inspect Rails, Puma, Nginx, and PostgreSQL logs for authentication failures, missing assets, permission errors, and repeated restarts.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Restart each supervised service deliberately and verify that it returns automatically and that the application remains able to connect to PostgreSQL.
Test a real database-backed route, not only a static health page. This catches incorrect credentials, missing migrations, and production-only configuration errors.
Troubleshoot by symptom
Rails reports password authentication or database-not-found errors
- Compare the database name, role, host, port, and password in the running environment with the values used in the successful
psqltest. - Check whether
DATABASE_URLis overriding or merging withdatabase.ymlunexpectedly. - Confirm that the PostgreSQL role has access to the database and that PostgreSQL is listening on the address Rails uses.
Puma starts and immediately exits
- Read the systemd journal or container logs for Bundler, Ruby, missing secret, and migration errors.
- Run the Puma command manually as the deploy user from the application’s
currentdirectory. - Verify that systemd uses the rbenv shim path and loads the same environment variables available in an interactive shell.
Nginx returns 502 Bad Gateway
- Check that Puma is active and listening on the configured loopback port or socket.
- Compare
proxy_passwith Puma’s bind setting. - Inspect permissions when using a Unix socket; the Nginx worker must be able to access it.
HTTPS issuance fails
- Resolve the hostname from an external network and confirm it returns the VPS address.
- Open port 80 and ensure Nginx or the Kamal proxy answers the certificate challenge.
- Remove stale DNS records that direct the hostname to another server.
Deployments run out of memory
- Check whether Docker images, build artifacts, PostgreSQL, and Ruby workers compete for the VPS memory budget.
- Reduce Puma workers or database connection pools to match available resources.
- Review prepared-statement usage; Rails’ default limit can consume substantial memory across many connections.
Operate the VPS after launch
Keep the Ruby version, Ubuntu base, PostgreSQL major version, gems, and deployment configuration under explicit change control. Apply operating-system and dependency updates during a maintenance window, and test migrations against a database copy when they alter large or heavily used tables.
Back up PostgreSQL independently of the application release. A code rollback cannot restore deleted data, and a VPS snapshot alone may not provide a consistent database recovery point. Document where backups are stored, how credentials are recovered, and how to restore the database before an incident occurs.
Monitor disk space, memory, CPU, PostgreSQL connections, Puma restarts, Nginx errors, certificate expiry, and failed jobs. Set alerts for repeated systemd restarts and authentication failures rather than waiting for users to report an outage.
Quick Recap
Production readiness checklist
- Ubuntu LTS is patched and inbound traffic is limited to required ports.
- The application runs under a dedicated deploy account, not root.
- The Ruby version matches
.ruby-versionand the lockfile’s Bundler requirements. - PostgreSQL has a dedicated application role and database; superuser credentials are not in Rails.
- Rails production settings use environment-backed secrets and an explicit
RAILS_ENV=production. DATABASE_URLanddatabase.ymlhave a documented source of truth.- Advisory-lock and prepared-statement settings match the database topology and memory budget.
- Migrations and assets run as part of the release procedure.
- Puma is supervised by systemd or Kamal, and Nginx is the public reverse proxy.
- DNS resolves to the VPS before TLS issuance, and HTTPS renewal is automated.
- Logs, database connectivity, public HTTPS, and restart behavior have all been tested.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




