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.
Creating & Deploying an Application
Follow these steps to deploy your Ruby application to production in under two minutes.
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.
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.
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
Dockerfileordocker-compose.ymlfor full control.
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.- Create the app with the Nixpacks build pack and deploy it.
- Create your database under Dashboard → Databases (see Configuring PostgreSQL).
- Add the database connection variables (e.g.
DATABASE_URL) to your app's environment variables and redeploy. Your app is up and running.
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.
cat config/master.key
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:
bundle exec rails secret
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).
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=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']:
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)
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
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 -Fcarchive. Smallest file; restore it withpg_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:
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.
# 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.
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>becomesOWNER 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 ROLEand 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_dumpthan the server may not restore. Export with apg_dumpno 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.
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=redis://enkihost-redis-app:6379/0
2. Configuring Sidekiq (config/initializers/sidekiq.rb)
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') }
end3. Configuring ActionCable (config/cable.yml)
production:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
channel_prefix: my_app_productionRuby on Rails Dockerfile
A production-ready, multi-stage Dockerfile optimized for Rails 7 / 8 with PostgreSQL, asset precompilation, jemalloc, and a non-root user.
# 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"]Sinatra Dockerfile
A fast, minimalistic Dockerfile for Sinatra applications running on Puma.
# 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"]Jekyll Dockerfile
Multi-stage build that compiles your Jekyll static site and serves it via an ultra-lightweight Alpine Nginx server.
# 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;"]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.
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: bridgeReady to deploy your Ruby application?
Get started with isolated containers, automated SSL, custom domains, and managed databases in seconds.