What I learned deploying a Rails app in One day
Why I wanted a boring, real deployment
Over-preparing and chasing exciting tools are both procrastination wearing a productivity suit. What I needed instead was rolling up my sleeves and getting my hands dirty. I stopped preparing and attempting to fully understand all the minute details of how a deployment works. I stopped looking for the latest or greatest shiny tools for deployments. Instead, I reached for tools that make the deployment quick and simple, and started the deployment process.
To me, boring means simple, familiar, battle-tested, and requiring fewer decisions. Instead of AWS as my VPS, I chose DigitalOcean because it’s familiar to me and less configurations to setup. I chose Rails 8 with kamal built-in for deployment because I didn’t need to build my whole deployment pipeline. I could accept the defaults with only a few configurations in one file, and my project was ready to deploy. Instead of choosing other fancy DNS providers full of features and price models, I opted for the boring old Cloudflare that everyone uses because it’s predictable and battle-tested. Novelty distracts while boring helps me focus on my goal: deploying my project.
By real, I mean acting instead of preparing. No deployment tutorials or guides. No digging into all the infrastructures that are involved in a deployment. No researching on which platform or VPS to use for my deployment. Real means finding an example, copying the steps, and starting to act. That generates real problems. Then you can solve those problems with more actions. As opposed to a bunch of ‘what if’ problems such as “What if this VPS doesn’t scale?”, “What if kamal doesn’t give me enough freedom to configure other settings?” etc… Those are fake, unreal. They pull you away from progress.
The stacks and tools I chose
I deployed a Rails 8 app with Tailwind as my CSS framework. Rails 8 comes with Kamal and SQLite by default, which made it easy to get started without extra setup. Kamal is a deployment tool that simplifies deployment process by containerizing the app and deploying to a remote linux machine such as a DigitalOcean’s droplet. SQLite is the default database in Rails 8, and since this app is just a simple blog, it’s more than sufficient for my needs. I chose Tailwind as my CSS framework because I’m already familiar with it. Tailwind’s utility-first approach lets me style directly in the HTML file without writing separate CSS files. I could’ve gone with something simpler like bootstrap, but Tailwind gives me more flexibility, and the trade-off felt worth it.
I use DigitalOcean as my VPS provider because it’s simple and familiar to me, it helped me avoid extra cognitive load that comes with configuring more complex platforms such as AWS. For DNS, I chose Cloudflare because it is widely used, battle-tested, and has a straightforward pricing model ($12/year), with no need to overthink plans or cost calculations.
What I learned from doing it this way
3 real problems I hit
When I first deployed using only an IP address as the host in deploy.yml, I kept getting SSL protocol errors saying the connection was insecure. It was confusing because I didn’t understand what SSL was or where the error was coming from. After digging around, I realized that SSL expects a domain name, not a raw IP address. Since I didn’t have a domain name at the time, I temporarily disabled SSL to get the deployment working before fixing it properly later.
After deploying the app successfully, I noticed that the CSS in production looked different from what I saw locally. The styles weren’t completely broken, but things such as the spacings and layouts felt off. I initially assumed it was a caching issue or that docker was using old assets. But the problem persisted even after recompling assets inside the production container,. Eventually, I found a post describing a bug related to Docker’s virtualization framework on Apple silicon, which caused Tailwind assets to compile incorrectly. Disabling a specific virtualization option fixed the issue.
I didn’t have a strong assumption about data persistence at first. The concern came up because an LLM suggested that I needed to take extra steps to make the database persist across deployments. That confused me because my posts in production were not being wiped out even after multiple deploys. I spent some time verifying where the SQLite database lived and how Rails was configured. In the end, I realized that Rails 8’s default SQLite setup already persisted the database through the existing deploy.yml volume configuration, so no additional changes were needed.
What I’d do differently next time
- I’d get a domain name ready before deploying. That would save me from temporarily disabling the SSL and fixing it later.
- I’d spend less time preparing and planning, and more time shipping; Even if the result is ugly or full of flaws.
- I’d accept the defaults more, and only customize after I understand how the default works and confirm that the customization is actually needed.
- I’d also trust my instincts more and treat LLMs as assistants, not substitutes for my own thinking.
- I’d be more comfortable not understanding everything at first and focus on maintaining momentum through iterations. Understanding tends to follow actions.
This setup is good for:
- Beginner developers .
- New grads who have never deploy an app end to end.
- Hobbyists who build projects but avoid deployments because it feels complex or intimidating.
- Side projects.
- Anyone who wants to learn by shipping instead of over-preparing.
This setup is NOT good for:
- Established teams
- Highly scaled applications.
- Projects that require heavy customization or complex infrastructure.
This reflects my understanding at the time; I expect it to evolve as I build and deploy more.