Rails_
Use Ruby on Rails and ActiveRecord with an Appwrite native PostgreSQL database. Configure database.yml, run migrations against the direct database port, and size the ActiveRecord pool.
5 min read
A native PostgreSQL database is a standard PostgreSQL engine, so Ruby on Rails works against it through ActiveRecord with no Appwrite-specific configuration. Point config/database.yml at the connection details from the Connections page and use ActiveRecord, migrations, and the rest of the Rails toolchain exactly as you would against any PostgreSQL server.
You'll need a native PostgreSQL database in a ready state and its credentials. In the Appwrite Console, open the database and click Credentials. Use the Details tab for individual values, or copy a ready-made string from the DSN, .env, Prisma, Drizzle, or psql tab. You can also call postgresql.get() from the Appwrite API to read hostname, connectionUser, connectionPassword, and connectionString. The primary user is admin, and the database name is generated for each database.
Install the database driver
ActiveRecord talks to PostgreSQL through the pg driver gem. Add it to your Gemfile:
# Gemfilegem 'pg'The pg gem builds against libpq, so the PostgreSQL client headers must be available at install time.
Then install:
bundle installSet the connection string
Copy the connection string from the Console Credentials dialog, or fetch it with the API. Keep it in an environment variable and do not commit it:
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"The TLS parameter (sslmode=require) is already part of the string Appwrite returns. Appwrite Cloud terminates TLS at the edge, so no extra certificate configuration is needed. For full certificate verification (verify-full) or mTLS, see the Network page.
Configure database.yml
Rails reads DATABASE_URL automatically. The simplest configuration points the url at the environment variable and lets ActiveRecord parse the host, port, database, and credentials out of it:
# config/database.ymlproduction: adapter: postgresql url: <%= ENV["DATABASE_URL"] %> pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>If you'd rather set the fields explicitly, the discrete keys map one-to-one to the values from the Connections response. Use ERB to read each value from the environment so no secret lands in source control:
# config/database.ymlproduction: adapter: postgresql host: <%= ENV["DB_HOST"] %> # db-<hash>.<region>.appwrite.center port: <%= ENV.fetch("DB_PORT", 5432) %> database: <%= ENV["DB_NAME"] %> # <database> username: <%= ENV.fetch("DB_USER", "admin") %> password: <%= ENV["DB_PASSWORD"] %> sslmode: require pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>When both DATABASE_URL and explicit keys are present, Rails merges them. sslmode and pool can still be set in database.yml, so a url-based config can carry the TLS parameter in the string and override pool in the YAML.
Size the connection pool
ActiveRecord manages a per-process connection pool. The pool: value caps how many backend connections a single Rails process holds, and it defaults to 5. It must be large enough for every thread that checks out a connection, your Puma worker threads plus any background job threads in the same process.
production: adapter: postgresql url: <%= ENV["DATABASE_URL"] %> pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>Tying pool to RAILS_MAX_THREADS keeps it aligned with Puma's thread count. Each Puma worker is a separate process with its own pool, so the backend connection count is roughly pool × workers × server instances. Keep that product within your specification's maxConnections, see specifications.
Run migrations
Generate and apply migrations the usual way:
bin/rails db:migrateMigrations issue DDL and need a session-level connection, so run them against the direct PostgreSQL port 5432, not the transaction-mode pooler. Point DATABASE_URL (or a separate migration URL) at the direct port when you run db:migrate. The primary admin user owns the database and can run schema changes. Narrower database roles should only receive the privileges your application needs.
Use ActiveRecord
Once database.yml is configured, models work with no further setup. Define a migration and model, then query through ActiveRecord:
# db/migrate/20240101000000_create_users.rbclass CreateUsers < ActiveRecord::Migration[7.1] def change create_table :users do |t| t.string :email, null: false t.timestamps end add_index :users, :email, unique: true endendDefine the matching model:
# app/models/user.rbclass User < ApplicationRecord validates :email, presence: true, uniqueness: trueendCreate and query records as usual:
User.create!(email: 'ada@example.com')
recent = User.order(created_at: :desc).limit(10)ActiveRecord opens connections lazily and reuses them from the pool, so a long-running Puma server keeps a small, stable set of backend connections rather than opening one per request.
Pooling for a long-running server
A Rails app under Puma is a long-running process: it holds an ActiveRecord pool for its lifetime. That pairs naturally with the direct PostgreSQL port 5432, sized so pool × workers stays within your connection budget. This is the recommended setup for a persistent server.
If you instead route through the connection pooler to absorb spikes or many app instances, prefer session mode, which keeps a backend connection for the whole client session and behaves like a direct connection to ActiveRecord. The pooler defaults to transaction mode, which hands out a different backend connection per transaction. ActiveRecord uses server-side prepared statements by default, and those are tied to one backend connection, so on the transaction-mode pooler you must disable them:
production: adapter: postgresql url: <%= ENV["DATABASE_URL"] %> # pooler host, port 6432 pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %> prepared_statements: falseSee the pooler page for the mode trade-offs. Whichever runtime connection you choose, always run bin/rails db:migrate against the direct PostgreSQL port.
Use a branch for previews and CI
Branches are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:
- Create a branch from the API and read its
connectionString. - Export it as
DATABASE_URLfor the job. - Run
bin/rails db:migrateand your test suite against the branch. - Delete the branch when the job finishes.
A branch has no pooler and exposes the PostgreSQL port directly, which gives migrations the session-level connection they need. Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.
Related
Connections
Retrieve credentials, rotate the password, and create database roles.
Connection pooler
Pool modes, ports, and read/write splitting for high-concurrency workloads.
Branches
Ephemeral database copies for preview environments and CI.
Network
TLS modes, certificate verification, mTLS, and IP allowlists.
For ActiveRecord and migration details beyond this guide, see the Rails configuration guide.
Was this page helpful?
Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.