Part 1 : Happy ending
05-09-2025 - It workings I did manage to deploy using kamal and
Assumiung you have a rails application setup with postgres db
Replace
config/deploy.yml
# Name of your application. Used to uniquely configure containers.
service: form_build
# Name of the container image.
image: 2112akilesh/form_build
# Deploy to these servers.
servers:
web:
- 140.245.16.xxx
# job:
# hosts:
# - 192.168.0.1
# cmd: bin/jobs
# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
#
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
proxy:
ssl: true
host: form.akilesh.in
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: 2112akilesh
# Always use an access token rather than real password when possible.
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .kamal/secrets).
env:
secret:
- RAILS_MASTER_KEY
- POSTGRES_PASSWORD
clear:
# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
# When you start using multiple servers, you should split out job processing to a dedicated machine.
SOLID_QUEUE_IN_PUMA: true
# Set number of processes dedicated to Solid Queue (default: 1)
# JOB_CONCURRENCY: 3
# Set number of cores available to the application on each server (default: 1).
# WEB_CONCURRENCY: 2
# Match this to any external database server to configure Active Record correctly
# Use form_build-db for a db accessory server on same machine via local kamal docker network.
# DB_HOST: postgresql://postgres:postgres@140.245.16.247:5432/form_build-db
DB_HOST: form_build-db
POSTGRES_USER: form_build
POSTGRES_DB: form_build_production
# Log everything from Rails
# RAILS_LOG_LEVEL: debug
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole"
# Use a persistent storage volume for sqlite database files and local Active Storage files.
# Recommended to change this to a mounted volume path that is backed up off server.
volumes:
- "form_build_storage:/rails/storage"
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
asset_path: /rails/public/assets
# Configure the image builder.
builder:
arch: amd64
# Use a different ssh user than root
ssh:
user: ubuntu
# Then, configure the `postgres` accessory
accessories:
db:
image: postgres:latest
host: 140.245.16.247
# Change to 5432 to expose port to the world instead of just local network.
port: "127.0.0.1:5432:5432"
# options:
# restart: always
env:
clear:
DB_HOST: form_build-db
POSTGRES_USER: form_build
POSTGRES_DB: form_build_production
secret:
- POSTGRES_PASSWORD
files:
- config/setup.sql:/docker-entrypoint-initdb.d/setup.sql
volumes:
- data:/var/lib/postgresql/data
# directories:
# - data:/var/lib/postgresql/data
config/setup.sql
CREATE DATABASE form_build_production;
CREATE DATABASE form_build_production_cache;
CREATE DATABASE form_build_production_queue;
CREATE DATABASE form_build_production_cable;
config/database.yml
# PostgreSQL. Versions 9.3 and up are supported.
#
# Install the pg driver:
# gem install pg
# On macOS with Homebrew:
# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
# On Windows:
# gem install pg
# Choose the win32 build.
# Install PostgreSQL and put its /bin directory on your path.
#
# Configure Using Gemfile
# gem "pg"
#
default: &default
adapter: postgresql
encoding: unicode
# For details on connection pooling, see Rails configuration guide
# https://guides.rubyonrails.org/configuring.html#database-pooling
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: form_build_development
password: <%= ENV["POSTGRES_PASSWORD"] %>
# The specified database role being used to connect to PostgreSQL.
# To create additional roles in PostgreSQL see `$ createuser --help`.
# When left blank, PostgreSQL will use the default role. This is
# the same name as the operating system user running Rails.
#username: form_build
# The password associated with the PostgreSQL role (username).
#password:
# Connect on a TCP socket. Omitted by default since the client uses a
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
#host: localhost
# The TCP port the server listens on. Defaults to 5432.
# If your server runs on a different port number, change accordingly.
#port: 5432
# Schema search path. The server defaults to $user,public
#schema_search_path: myapp,sharedapp,public
# Minimum log levels, in increasing order:
# debug5, debug4, debug3, debug2, debug1,
# log, notice, warning, error, fatal, and panic
# Defaults to warning.
#min_messages: notice
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: form_build_test
# As with config/credentials.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password or a full connection URL as an environment
# variable when you boot the app. For example:
#
# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
#
# If the connection URL is provided in the special DATABASE_URL environment
# variable, Rails will automatically merge its configuration values on top of
# the values provided in this file. Alternatively, you can specify a connection
# URL environment variable explicitly:
#
# production:
# url: <%= ENV["MY_APP_DATABASE_URL"] %>
#
# Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full overview on how database connection configuration can be specified.
#
production:
primary: &primary_production
<<: *default
host: <%= ENV["DB_HOST"] %>
database: form_build_production
username: form_build
password: <%= ENV["POSTGRES_PASSWORD"] %>
cache:
<<: *primary_production
database: form_build_production_cache
migrations_paths: db/cache_migrate
queue:
<<: *primary_production
database: form_build_production_queue
migrations_paths: db/queue_migrate
cable:
<<: *primary_production
database: form_build_production_cable
migrations_paths: db/cable_migrate
Dockerfile
# syntax=docker/dockerfile:1
# check=error=true
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t form_build .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name form_build form_build
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.2
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here
WORKDIR /rails
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libpq-dev libyaml-dev 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 && \
bundle exec bootsnap precompile --gemfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage for app image
FROM base
# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
USER 1000:1000
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"
If you use any other database or get a config with your Docker_user_name, Application_name, serverip, Domain_name use this tool
https://dailydevtools.com/kamal_config
Part 2 : Sad Ending
Not a bid fan of kamal-deploy but the hype around it is real
You need to do some prep work before jumping in
- Create a VPS
- Add Fire wall rules (Enable port 80 & 443)
- Connecting VPS using SSH
- Add
Arecord to your DNS - Lets deploy ← In this blog
Install kamal
Assuming you have ruby installed
gem install kamal
Setup docker in your local and VPS
Install docker locally I am using Debian refer this **link & additionally I wanted to install tis package to**
sudo apt update
sudo apt install -y [docker.io](http://docker.io/)
apt install docker-buildx-plugin
Kamal SSH setup for non root user run the below command in your VPS
sudo apt update
sudo apt upgrade -y
sudo apt install -y docker.io curl git
sudo usermod -a -G docker app
Start Docker server locally
sudo systemctl start docker // Start Docker
sudo systemctl status docker // Check Docker Status
sudo usermod -aG docker $USER // Making sure kamal had access to run docker without sudo privilege
you might also want to set permission to instance
sudo usermod -aG docker ${USER}
Restart your device
inside our config/deploy
lets set thing up
add your image name
Paste your VPS ipv4 to servers:
Uncomment your ssh: line and add your ssh config it may vary depending on your case for me I need to use different user name instead of root refer doc if you have other case
Make sure your .yml file is formatter correctly otherwise you will get error similar to
ERROR (Psych::SyntaxError): (<unknown>): did not find expected key while parsing a block mapping at line 2 column 1go to your docker hub and create a access token make sure to give read and write access copy your access key and save it in a
.env
KAMAL_REGISTRY_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Push your env to kamal
kamal env push
or add it in your local linux environment (I personally went with this)
export KAMAL_REGISTRY_PASSWORD=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
and restart your file in your project directory
kamal setup
If you face your self with Releasing the deploy lock error run this command
kamal lock releaseto release the lock nice little feature
I am using postgres database so ill configure it to
update your accessories: & production: section in deploy.yml
update the init file
files:
- config/init.sql:/docker-entrypoint-initdb.d/setup.sql
create a init file
I tried kamal with rails 5 and docker I somehow find myself stuck in between error even after multiple hours of googling and successfully deploying it initially the following build fails I just gave up now back with kamal and rails 8 ( I know I am not clear with my error and explanation but it is what it is fuck off)
Even while writing this post I went through a hard time to figure our what went wring and how to fix it and even for the n-th time I coudnt able to pull it out in 1st try but yes its like
Add A name record to your DNS pointing it to your VPS ip address
- day 3 still Kamal deploy fucks me
- I dont think you will get a perfectly polished outcome after this blog its trial and error method
in your .kamal/secret pass production.key
# RAILS_MASTER_KEY=$(cat config/master.key)
RAILS_MASTER_KEY=$(cat config/credentials/production.key)
Pass your application host name
DB_HOST=
31/05/2025 — Not giving up today
Starting from scratch
Use this tool to generate kamal config https://dailydevtools.com/kamal_config
Delete the database in the server if already exist
Other Tools to check https://containrrr.dev/watchtower/
Enable port 80 & 443 check https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-with-ufw-on-ubuntu#step-1-making-sure-ipv6-is-enabled
Fuck Kamal I am switching to plain DOCKER implementation
29 April 2026
Use latest Ubuntu server 22.04+
-
If old Ubuntu versoin is used install docker manually
sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc > /dev/null sudo chmod a+r /etc/apt/keyrings/docker.asc echo \ "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu focal stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update
```jsx
sudo systemctl enable --now docker
sudo usermod -aG docker ubuntu
```
Prepare deploy.yml
# Name of your application. Used to uniquely configure containers.
service: drop_share
# Name of the container image (use your-user/app-name on external registries).
image: 2112akilesh/drop_share
# Deploy to these servers.
servers:
web:
- 141.148.209.128
proxy:
ssl: false
host: dropshare.akilesh.in
registry:
username: 2112akilesh
# Access token stored in `.kamal/secrets`
password:
- KAMAL_REGISTRY_PASSWORD
# Inject ENV variables into containers (secrets come from .kamal/secrets).
env:
secret:
- RAILS_MASTER_KEY
clear:
SOLID_QUEUE_IN_PUMA: false
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
aliases:
console: app exec --interactive --reuse "bin/rails console"
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole --include-password"
# Use a persistent storage volume for sqlite database files and local Active Storage files.
# Recommended to change this to a mounted volume path that is backed up off server.
volumes:
- "drop_share_storage:/rails/storage"
- "db:/rails/db"
asset_path: /rails/public/assets
builder:
arch: amd64
# I have stored my ssh key in <my_project>/ssh/my_ssh.key
# dont forget to add ssh to .gitignore
ssh:
user: ubuntu
keys:
- ssh/ssh-key-2026-04-14.key
Add custom domain
configure your DNS
add a A record with vps public ip addresss
Optimise VPS
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
kamal setup
kamal deploy

Top comments (0)