Back to all posts

Deploying a Complete CRM: Rails 8 Rapid Prototyping to Production in 15 Minutes (Part 4)

June 9, 2025
Robin Goudeketting
10 min read
mvp developmentrapid prototypingcrmruby on railsrails 8 tutorialminimum viable productkamaldigitaloceanweb developmentstartup mvp developmentdockersolid queuesolid cablesolid cache
Deploying a Complete CRM: Rails 8 Rapid Prototyping to Production in 15 Minutes (Part 4)

TL;DR

In this final installment, we deploy our complete CRM to production using Rails 8's built-in Docker integration and Kamal for orchestration. We configure DockerHub as our container registry, set up a DigitalOcean droplet with managed PostgreSQL, and configure Rails 8's solid_queue, solid_cache, and solid_cable for production-ready background processing. With proper environment configuration and DNS setup, we achieve a fully functional, SSL-secured production deployment in under 15 minutes. Combined with our 2-hour development time, we've gone from concept to live, stakeholder-accessible CRM in just over 2 hours total—demonstrating Rails' unmatched speed for MVP validation.

Introduction

After finishing the CRM last week, we should now deploy it. Rails 8 ships with docker1 integration and Kamal2 is made for easily managing these deployments. We'll deploy to DigitalOcean3 because of its easy scaling and simple setup, but first a reminder why this is important.

Why we're doing this and why it matters for your business: When stakeholders can interact with a functional prototype that looks and feels production-ready, buy-in happens 3x faster than with static mockups. I've seen this repeatedly with B2B SaaS companies validating new features—functional beats beautiful every time when it comes to early validation.

We're going to add a timer to track how long it takes to get our CRM live. My estimation is that it takes less than an hour, but let's see if Rails can surprise us again.

Our setup

Before we begin, let's talk through the setup we'll be using for deploying. Kamal uses Docker and DockerHub out of the box to deploy our application. In essence, what Kamal will be doing once we run a deploy is create a new image (or update it with the newest changes) on DockerHub and subsequently push it to our DigitalOcean droplet. That means there are a few separate elements we need to set up.

Let's deploy this CRM and demonstrate why Rails remains unmatched for business validation speed!

Account setup

⏱️ Elapsed time: 00:00

First, we should create accounts for DockerHub and DigitalOcean. I just used my GitHub account, since that's what I use for all my development interactions. Of course, we also need a domain to deploy it to. I'll use a subdomain on my own website, but the setup will be the same for a normal domain.

These foundational accounts create our deployment pipeline—the invisible infrastructure that transforms your validated prototype into a stakeholder-accessible application.

Deploy configuration

⏱️ Elapsed time: 01:30

DockerHub

We're going to need the account information from DockerHub to handle deploys correctly. Copy your username from DockerHub and paste it into deploy.yml in our application in the following places:

# config/deploy.yml
image: <your-username>/databae
# ...
registry:
  username: <your-user>
>_ yml

For me, that looks as follows:

image: rmgoudeketting/databae
# ...
registry:
  username: rmgoudeketting
>_ yml

If you've looked through the deploy.yml file a little, you see that there's a variable called KAMAL_REGISTRY_PASSWORD. This is the password that kamal uses to authenticate DockerHub and crucial to make our deployments work. Let's make an access token in DockerHub. For this, go to DockerHub and locate your Account Settings, from where you should be able to find Personal Access Tokens (or click here to go there directly). Generate a new token, give it a recognisable description, and ensure Read, Write, Delete access permissions.

DockerHub token generation interface showing access permissions

Once you click generate, you should see the following: Generated DockerHub access token display

Next, we should create a .env file. I personally like to create environment specific .env files, so let's create .env.production. Don't forget to add them to the .gitignore if they're not in there by default. Now go back to DockerHub and copy the personal access token. It should look something like dckr_pat_..... We should add this to our .env.production file like so:

KAMAL_REGISTRY_PASSWORD=dckr_pat_...
>_ text

At the top of our deploy.yml, add the following line:

<% require "dotenv"; Dotenv.load(".env.production") %>
>_ yml

DigitalOcean

Next, we need to set up a droplet on DigitalOcean. Log in to DigitalOcean and create a new project. Name it something you can remember, I'll just go for DataBae. It then asks you to move resources, but you can skip that step. Next, look for the button that says 'Create Droplet' or 'Spin up a droplet'. This is what our server is going to run on. The great thing about DigitalOcean is that they charge you by the minute. Once you're done showing your application to your stakeholders, you can destroy the droplet and database and you stop paying immediately.

  • Make sure to select the region closest to you for the best performance.
  • I kept the operating system to the set default, but if you run a specific version for your app, definitely choose that.
  • For size and CPU options, I went the smallest I could go, $4/month.
  • There's free improved metrics monitoring and alerting, so no reason not to tick that box.
  • For authentication, select SSH and create a new SSH key. For this, just follow the instructions that DigitalOcean provides and you should be set.
  • Select the managed database. This is the most expensive bit of our application at $15/month. However, it includes unlimited managed databases in the cluster, so you could connect more than one application to it that would still have their own databases.
  • For the database type, select PostgreSQL
  • Then click Create Droplet

Once the droplet spins up, you can copy the IP address and paste it into your deploy.yml configuration:

# config/deploy.yml
servers:
  web:
    - 165.227.173.198
>_ yml

Next, we have to configure the proxy to make sure it points to the right domain. First in deploy.yml:

# config/deploy.yml
proxy:
  ssl: true
  host: databae.goudeketting.nl
>_ yml

And then add the configuration to your hosting provider. Since I'm adding it as a subdomain, I added the following A record:

DNS management interface showing A record configuration

Database connection

For the database connection, we can make a database in the cluster we just launched. For this, click on your database cluster and then go to Users & Databases. If you scroll down, you can add a new Database Name. I called mine databae. Go back to the project overview and click the three dots of the cluster to find the connection details. Select the connection string and copy it.

DigitalOcean database cluster dropdown menuDatabase connection string interface

Open our .env.production and add:

DATABASE_URL=<connection string>
>_ text

Then open .kamal/secrets and add the following line below the KAMAL_REGISTRY_PASSWORD

DATABASE_URL=$DATABASE_URL
>_ text

And also, let's add the DATABASE_URL to our deploy.yml:

# config/deploy.yml
env:
  secret:
    - RAILS_MASTER_KEY
    - DATABASE_URL
>_ yml

There are a few more things we have to do before we can actually run the deploy. For starters, we have to make sure that we have docker installed. I use Docker Desktop on my Mac, but you might need a different installation. You can find the right installation for your system on docs.docker.com.

Database setup

⏱️ Elapsed time: 05:23

In less time than a typical stakeholder meeting, we've configured enterprise-grade infrastructure. Now let's set up Rails 8's powerful solid foundation.

Rails comes with a lot of specialised tools out of the box, the most important ones for our deploy are solid_queue, solid_cable, and solid_cache. These provide background workers, a websocket adapter, and caching. We're not actually using them right now, but they need to be configured for our deploy to work. Let's do that now.

First, we need to generate the migrations. Run the following commands

rails g migration AddSolidQueueTables
>_ bash
rails g migration AddSolidCacheTables
>_ bash
rails g migration AddSolidCableTables
>_ bash

And then copy the content from the db/queue_schema.rb, db/cache_schema.rb, and db/cable_schema.rb to the change methods of the newly generated migrations. Don't forget to run rails db:migrate.

Then, we need to update the database.yml, queue.yml, cable.yml, and cache.yml with the following content:

# config/database.yml
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:
  primary: &primary_development
    <<: *default
    database: company_crm_development
  cache:
    <<: *primary_development
  queue:
    <<: *primary_development
  cable:
    <<: *primary_development
 
test:
  <<: *default
  database: company_crm_test
 
production:
  primary: &primary_production
    <<: *default
    url: <%= ENV["DATABASE_URL"] %>
  cache:
    <<: *primary_production
  queue:
    <<: *primary_production
  cable:
    <<: *primary_production
>_ yml
# config/queue.yml
default: &default
  dispatchers:
    - polling_interval: 1
      batch_size: 500
  workers:
    - queues: "*"
      threads: 3
      processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %>
      polling_interval: 0.1
 
development:
  <<: *default
 
test:
  <<: *default
 
production:
  <<: *default
>_ yml
# config/cable.yml
development:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day
 
test:
  adapter: test
 
production:
  adapter: solid_cable
  connects_to:
    database:
      writing: cable
  polling_interval: 0.1.seconds
  message_retention: 1.day
>_ yml
# config/cache.yml
default: &default
  store_options:
    # Cap age of oldest cache entry to fulfill retention policies
    # max_age: <%= 60.days.to_i %>
    max_size: <%= 256.megabytes %>
    namespace: <%= Rails.env %>
 
development:
  <<: *default
 
test:
  <<: *default
 
production:
  database: cache
  <<: *default
>_ yml

And then lastly, we need to update the production and development configs with the same code:

# config/environments/development.rb & config/environments/production.rb
Rails.application.configure do
  # ...
  # Replace the default in-process memory cache store with a durable alternative.
  config.cache_store = :solid_cache_store
 
  # Replace the default in-process and non-durable queuing backend for Active Job.
  config.active_job.queue_adapter = :solid_queue
  config.solid_queue.connects_to = { database: { writing: :queue } }
  # ...
end
>_ ruby

Deploying

⏱️ Elapsed time: 12:32

All that's left now is running kamal setup in the root of our project and watch the magic happen. It renders a lot of red text, but in this case, red is okay. As long as it keeps outputting new lines and there are no mentions of errors or fail, you're fine! The first setup takes a bit longer, since all the gems and packages still have to get installed. The subsequent deploys will be quicker, since all the gems will be cached at that point.

Three minutes later, just like that, our application is live!

CRM login page showing professional authentication interfaceCRM dashboard displaying company overview and metricsCompany creation form with industry categorization

Conclusion

⏱️ Elapsed time: 14:14

From Zero to Production in just over 2 hours

We've just completed a remarkable journey—transforming a business concept into a fully functional, production-deployed CRM system across four focused articles. This wasn't a toy application or a proof-of-concept demo. We built a genuine business tool with user authentication, role-based company management, industry categorization, responsive design, and enterprise-grade deployment infrastructure.

What We Accomplished:

  • Part 1 (35 min): Core application foundation with models, associations, and professional Bootstrap interface
  • Part 2 (40 min): Enterprise authentication, user relationships, and interactive dashboard with data visualization
  • Part 3 (45 min): Advanced filtering, pagination, and custom Stimulus components for production-quality UX
  • Part 4 (15 min): Production deployment with SSL, managed database, and scalable infrastructure

This progression demonstrates Rails' extraordinary power for systematic business validation. While other frameworks require extensive configuration, multiple services, and complex deployment pipelines, Rails delivered a complete solution with minimal friction at each stage.

The Business Impact

For startups, consultancies, and product teams, this approach transforms the validation timeline. Instead of spending weeks building and months deploying, you can have stakeholders interacting with functional prototypes within hours. This speed advantage compounds—faster validation means quicker pivots, earlier market feedback, and significantly reduced development risk.

Moving Forward

Our CRM is production-ready and can handle real business workflows immediately. From here, you might add contact management, email integration, reporting dashboards, or API endpoints—all building on the solid foundation we've established through this series.

Rails 8's4 integration of Docker, Kamal, and the solid_* libraries represents a maturation of the framework that makes it more compelling than ever for rapid prototyping. Combined with Hotwire's5 frontend capabilities, Rails continues to be the ultimate tool for transforming ideas into reality at startup speed.

The next time you have a business idea that needs validation, remember: with Rails, you're always just four articles away from a live, functional prototype. Ready to validate your next business idea at Rails speed? I help companies go from concept to stakeholder-ready prototypes in under a week. Let's talk about how rapid prototyping can accelerate your path to market.

Footnotes

  1. DockerHub: Cloud-native software deployment made easy. https://hub.docker.com/

  2. Kamal: Deploy web apps anywhere. From bare metal to cloud VMs. https://kamal-deploy.org/

  3. DigitalOcean: The simplest cloud that scales with you. https://www.digitalocean.com/

  4. Rails has extensive documentation with guides that can help you get started quickly. https://guides.rubyonrails.org/

  5. Stimulus: A modest JavaScript framework for the HTML you already have. https://stimulus.hotwired.dev/