Platform Overview

Enkihost Documentation

Enkihost is a modern cloud hosting platform engineered specifically for the Ruby ecosystem. We provide fully managed runtime environments for Ruby on Rails, Sinatra, and Jekyll, with automated Git deployments, isolated containers, zero-downtime rollouts, and dedicated PostgreSQL and Redis addons.

Zero-Downtime

New containers pass healthchecks before legacy instances are gracefully stopped.

Managed Addons

Instantly provision PostgreSQL 16 and Redis 7 and copy the connection URL into your app.

Encrypted Secrets

Store environment variables securely with AES-256 encryption at rest.


Step-by-Step Guide

Creating & Deploying an Application

Follow these steps to deploy your Ruby application to production in under two minutes.

1

Connect your Git Provider

Log in to the Enkihost console, navigate to Settings or New Application, and connect your GitHub or GitLab account. Grant access to your public or private repositories.

2

Select Repository and Branch

Choose the repository you wish to deploy and select the production branch (e.g. main or production). Any subsequent push to this branch will automatically trigger a new deployment.

3

Select Buildpack or Docker Engine

Enkihost supports automatic buildpacks as well as custom Docker configurations:

  • Ruby on RailsAutomatic asset compilation, database migrations, and Puma server setup.
  • SinatraLightweight rackup execution using Puma or Thin on your designated port.
  • JekyllBuilds static assets and serves them with zero-latency HTTP caching.
  • Docker / Docker ComposeUses your repository's Dockerfile or docker-compose.yml for full control.
No Dockerfile? Use NixpacksIf you don't have Docker set up or don't want to add a Dockerfile to your repository, pick the Nixpacks build pack (the default). It detects your language and framework from the source code and builds a production image automatically, so you don't need any Docker configuration.
  1. Create the app with the Nixpacks build pack and deploy it.
  2. Create your database under Dashboard → Databases (see Configuring PostgreSQL).
  3. Add the database connection variables (e.g. DATABASE_URL) to your app's environment variables and redeploy. Your app is up and running.
4

Configure Environment Variables

Provide critical runtime secrets. Rails apps need one of the two keys below, depending on whether your app uses encrypted credentials (config/credentials.yml.enc).

Your app uses encrypted credentials. Add a RAILS_MASTER_KEY variable in the Config tab and redeploy. Its value is the contents of config/master.key on your machine, which Rails created when you ran rails new. Enkihost can't generate it for you: it must be the same key that encrypted your credentials, and it never reaches the server because the file is git-ignored. Rails then reads secret_key_base from your credentials, so you don't need a separate SECRET_KEY_BASE.

Reading your local master key
bash
cat config/master.key
Without the master keyEvery Rails.application.credentials value (API keys, Stripe, AWS…) comes back as nil, and if config.require_master_key is enabled the app won't boot. Never commit config/master.key to your repository; keep it only in the Config tab. If you lost it, run bin/rails credentials:edit locally after deleting the old credentials.yml.enc to create a new key, and re-enter your secrets.

Your app doesn't use encrypted credentials. You need a SECRET_KEY_BASE instead. Open your app in the dashboard, go to the Terminal tab and run the command below. Then copy the generated value into a SECRET_KEY_BASE variable in the Config tab and redeploy:

Generating a secure Rails secret key
bash
bundle exec rails secret
5

Deploy & Access

Click Deploy Application. Enkihost builds the container, provisions the internal network, attaches your SSL certificate, and maps your custom or generated subdomain (e.g. my-app.enkihost.com).


Storage & Persistence

Configuring PostgreSQL

How to provision a managed PostgreSQL 16 database and connect it to your Ruby application.

1. Provisioning in Console

Navigate to Dashboard → Databases and click New Database. Select PostgreSQL to provision it.

2. Add DATABASE_URL to your App

DATABASE_URL is not added to your app automatically. Copy the connection URL from the database card in Dashboard → Databases, add it as a DATABASE_URL environment variable in your app's settings, and redeploy:

DATABASE_URL format
env
DATABASE_URL=postgres://enkihost_user:a8f93bc0912@enkihost-postgres-app:5432/my_app_production

3. Configuring Ruby on Rails (config/database.yml)

In Rails, configure your production database to read from ENV['DATABASE_URL']:

config/database.yml
yaml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: my_app_development

test:
  <<: *default
  database: my_app_test

production:
  <<: *default
  url: <%= ENV['DATABASE_URL'] %>

4. Configuring Sinatra (with Sequel or ActiveRecord)

Sinatra with Sequel (app.rb)
ruby
require 'sinatra'
require 'sequel'

# Connects to the PostgreSQL database set in the DATABASE_URL environment variable
DB = Sequel.connect(ENV['DATABASE_URL'] || 'postgres://localhost/my_sinatra_dev')

get '/users' do
  content_type :json
  DB[:users].all.to_json
end

Backups & Migrations

Downloading & Importing PostgreSQL Data

Take a copy of your database whenever you want, or bring your data over from another provider by uploading a dump.

1. Downloading your Database

In Dashboard → Databases, every running PostgreSQL card has two download buttons. The dump is generated on the fly with pg_dump and streamed straight to your browser; nothing is stored on our side.

  • .dump: compressed pg_dump -Fc archive. Smallest file; restore it with pg_restore.
  • .sql: plain SQL script. Readable and editable; restore it with psql.

Downloads are exported without owners or privileges, so they restore cleanly into any PostgreSQL server under whichever user you connect as:

Restore a download locally
bash
createdb my_app_development

# .dump archive
pg_restore --no-owner --no-privileges -d my_app_development my-app-db-20261019120000.dump

# .sql script
psql -d my_app_development -f my-app-db-20261019120000.sql

2. Creating a Dump to Import

The import accepts plain SQL (.sql), gzipped SQL (.sql.gz) and pg_dump custom archives (.dump, optionally gzipped). The format is detected from the file contents, not its extension. Copies of the data directory, pg_dump -Ft tar archives and ZIP files are not supported.

Export from your current provider
bash
# Recommended: custom-format archive
pg_dump -Fc --no-owner --no-privileges "$OLD_DATABASE_URL" > my_app.dump

# Or a plain SQL script, gzipped
pg_dump --no-owner --no-privileges "$OLD_DATABASE_URL" | gzip > my_app.sql.gz

3. Importing your Dump

Click import on the database card and choose your file (up to 2 GB). After the upload finishes the import runs in the background, and the card shows its progress, the final status and the full log, including any error reported by PostgreSQL.

Importing replaces all the data in the database. Download a copy first if you might need the current contents.

4. How Ownership is Handled

Your dump was created on another server, with its own roles (for example alice or postgres). Those roles don't exist here, so every object is recreated as the user in your connection URL (enkihost in postgres://enkihost:****@enkihost-addon-22:5432/main), and your app keeps working with the sameDATABASE_URL:

  • Every OWNER TO <role> becomes OWNER TO enkihost; the import log lists which roles were replaced.
  • Statements that only make sense on the source server are skipped: CREATE/ALTER ROLE, GRANT/REVOKE,CREATE/ALTER DATABASE, SET ROLE and psql meta-commands such as \connect.
  • Your table data is imported exactly as it is in the dump.

5. Safe, All-or-Nothing Imports

The dump is first restored into a separate scratch database. Only when the whole restore succeeds is it swapped in for your live database: open connections are closed for a moment and your app reconnects to the new data under the same name. If anything fails (a syntax error, a corrupt or truncated file, a missing extension) the scratch database is discarded and your live database is left untouched.

6. Troubleshooting

  • Unsupported version: a dump made by a newer pg_dump than the server may not restore. Export with apg_dump no newer than your database, or use plain SQL.
  • Extension not available: if the dump needs an extension that isn't installed (for example PostGIS), the import stops and the log names it.
  • Not enough disk space: a restored database with its indexes takes several times the size of a compressed dump; the import checks free space before it starts.
  • Import already running: only one import per database runs at a time. Wait for it to finish before starting another.

In-Memory Caching & Background Jobs

Configuring Redis

Set up Redis 7 for application caching, ActionCable WebSockets, and background workers with Sidekiq.

1. Add REDIS_URL to your App

Copy the connection URL of your Redis addon from Dashboard → Databases and add it to your app as a REDIS_URL environment variable:

REDIS_URL format
env
REDIS_URL=redis://enkihost-redis-app:6379/0

2. Configuring Sidekiq (config/initializers/sidekiq.rb)

config/initializers/sidekiq.rb
ruby
Sidekiq.configure_server do |config|
  config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
end

Sidekiq.configure_client do |config|
  config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0') }
end

3. Configuring ActionCable (config/cable.yml)

config/cable.yml
yaml
production:
  adapter: redis
  url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
  channel_prefix: my_app_production

Production Containerfile

Ruby on Rails Dockerfile

A production-ready, multi-stage Dockerfile optimized for Rails 7 / 8 with PostgreSQL, asset precompilation, jemalloc, and a non-root user.

Dockerfile (Ruby on Rails Production)
dockerfile
# syntax = docker/dockerfile:1

# 1. Base image with Ruby
ARG RUBY_VERSION=3.3.5
FROM ruby:$RUBY_VERSION-slim AS base

WORKDIR /rails

# Set production environment variables
ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development:test"

# Install base packages (libpq for PostgreSQL, curl for healthcheck)
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libpq-dev libjemalloc2 && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Enable jemalloc for reduced memory fragmentation in Ruby
ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"

# 2. Build stage for gems and asset precompilation
FROM base AS build

# Install build tools for native gem extensions (e.g. pg, nokogiri)
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git pkg-config && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git

# Copy application code
COPY . .

# Precompile assets without requiring a live database connection
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

# 3. Final execution stage
FROM base

# Run as non-root user for maximum container security
RUN useradd rails --create-home --shell /bin/bash && \
    chown -R rails:rails /rails

COPY --chown=rails:rails --from=build /usr/local/bundle /usr/local/bundle
COPY --chown=rails:rails --from=build /rails /rails

USER rails:rails

# Expose port (configured via ENV or defaults to 3000)
EXPOSE 3000
ENV PORT=3000

# Start Puma web server
CMD ["./bin/thrust", "./bin/rails", "server"]

Lightweight Microframework

Sinatra Dockerfile

A fast, minimalistic Dockerfile for Sinatra applications running on Puma.

Dockerfile (Sinatra Production)
dockerfile
# syntax = docker/dockerfile:1
FROM ruby:3.3-slim

WORKDIR /app

ENV RACK_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development:test"

# Install dependencies (libpq-dev for postgres, build-essential for native gems)
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential libpq-dev curl && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy dependency definitions
COPY Gemfile Gemfile.lock ./
RUN bundle install

# Copy application code
COPY . .

# Run as non-root user
RUN useradd -m sinatra && chown -R sinatra:sinatra /app
USER sinatra

EXPOSE 4567
ENV PORT=4567

# Launch via bundle exec puma or rackup
CMD ["bundle", "exec", "puma", "-p", "4567", "-e", "production"]

Static Site Generation

Jekyll Dockerfile

Multi-stage build that compiles your Jekyll static site and serves it via an ultra-lightweight Alpine Nginx server.

Dockerfile (Jekyll with Alpine Nginx)
dockerfile
# 1. Build stage: compile static site with Jekyll
FROM ruby:3.3-alpine AS builder

WORKDIR /srv/jekyll

RUN apk add --no-cache build-base gcc cmake git

COPY Gemfile Gemfile.lock ./
RUN bundle install

COPY . .
ENV JEKYLL_ENV=production
RUN bundle exec jekyll build --destination /srv/jekyll/_site

# 2. Production stage: serve with high-performance Nginx
FROM nginx:alpine

# Copy compiled static HTML/CSS/JS to webroot
COPY --from=builder /srv/jekyll/_site /usr/share/nginx/html

# Custom nginx config for SPA routing and caching if needed
RUN printf "server {\n\
    listen 80;\n\
    server_name localhost;\n\
    location / {\n\
        root /usr/share/nginx/html;\n\
        index index.html index.htm;\n\
        try_files \$uri \$uri/ /index.html;\n\
    }\n\
}\n" > /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Full Multi-Container Stack

Production docker-compose.yml

A complete docker-compose.yml demonstrating your web service orchestrated with PostgreSQL 16 and Redis 7, persistent volumes, and health checks.

docker-compose.yml
yaml
version: "3.8"

services:
  # 1. Main Web Application
  web:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - RAILS_ENV=production
      - PORT=3000
      - DATABASE_URL=postgres://enkihost:securepassword123@db:5432/my_app_production
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY_BASE=${SECRET_KEY_BASE}
      - RAILS_MASTER_KEY=${RAILS_MASTER_KEY}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - enkihost-network

  # 2. Managed PostgreSQL Database
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: enkihost
      POSTGRES_PASSWORD: securepassword123
      POSTGRES_DB: my_app_production
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U enkihost -d my_app_production"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - enkihost-network

  # 3. Managed Redis Cache & Queue
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - enkihost-network

volumes:
  postgres_data:
    driver: local
  redis_data:
    driver: local

networks:
  enkihost-network:
    driver: bridge
Get Started Today

Ready to deploy your Ruby application?

Get started with isolated containers, automated SSL, custom domains, and managed databases in seconds.