Apr 17, 2025

Migrating from Rails to Go: Making the Switch with Confidence

Learn from teams who have successfully migrated from Rails to Go, reducing their DevOps burden in the process.

9 Min Read
Ruby on Rails

Ruby on Rails has long been a go-to framework for building web applications quickly and elegantly. With its strong conventions, batteries-included approach, and productive developer experience, it's no surprise that countless startups and scaleups have built their core systems on Rails.

But as products grow and performance demands increase, many teams find themselves bumping into the limitations of dynamic languages and monolithic frameworks. That’s when Go enters the conversation.

In this post, we’ll explore why teams migrate from Rails to Go, the benefits and challenges of doing so, and show how using Encore.go (a Rails-like framework for Go) helps smooth the transition. We’ll also share stories from teams, who made the move and never looked back.

Why Migrate from Rails to Go?

Golang

There’s no denying Rails is fantastic for prototyping and early-stage development. But as your product and team scale, you may hit some familiar roadblocks that Go can help with:

  • Performance and Scalability: Rails apps can start to strain under high concurrency or CPU-intensive workloads. Go’s concurrency model (goroutines and channels) and compiled performance make it ideal for services that need to scale.
  • Operational Simplicity: Go’s static binaries and minimal dependencies make deployment easier and more predictable — no more wrangling Ruby versions, gem conflicts, or memory-hungry app servers.
  • Larger Engineering Teams: Go’s emphasis on simplicity and readability means codebases tend to be easier to maintain and onboard onto, especially for engineers coming from a variety of backgrounds.
  • Cost Efficiency: Because Go is more memory-efficient and can handle more concurrent requests per instance, companies often see significant cloud infrastructure cost savings after migrating.

The Challenge with Adopting Go: Rails Has… Rails

If you're coming from Rails, you're used to a world where nearly everything you need is built in. Rails isn't just a framework — it's a full ecosystem with powerful conventions and integrated tools that guide your development process. It helps you move quickly, with minimal configuration, and builds in best practices by default.

Go, by contrast, is famously minimal.

That minimalism is part of its power — Go gives you simple concurrency, lightning-fast compile times, and small, efficient binaries. But Go also leaves much more up to you and doesn't come with much out of the box. There is no built-in support for routing, background jobs, database migrations, or standard project layout. You get the basics like an HTTP server and a simple testing package, and you're left to decide how to structure everything else.

This is by design. Go favors minimalism and flexibility, but the tradeoff is that you have to make a lot of decisions yourself. That can mean spending more time upfront figuring out architecture, evaluating libraries, and setting up conventions, even for common tasks.

For many teams, especially those coming from frameworks like Rails, this can create friction. The lack of structure and defaults often surfaces in a few recurring challenges.

No Shared Conventions

Rails has a strong philosophy: “convention over configuration.” That means things like file structure, naming, request routing, and even testing are all consistent across projects and teams. In Go, there is no one “right” way — which leads to internal disagreements, reinvented wheels, and onboarding overhead.

Too Many Choices, Not Enough Guidance

In Rails, when you need to send emails or manage background jobs, you reach for ActionMailer or ActiveJob — tools that are battle-tested and first-class citizens in the framework. In Go, you're faced with dozens of third-party options for everything from job queues to database drivers, with no clear path forward.

Infrastructure Becomes a Time Sink

Rails doesn’t just handle code — it also comes with tools like rails db:migrate, rails server, and rails console that make development and deployment simple. In Go, setting up infrastructure often means managing Terraform, Docker, CI pipelines, secret managers, observability tools, and more — all manually. That makes every new service feel like a mini DevOps project.

Inconsistency Across Teams and Projects

Without Rails' unifying conventions, teams often find themselves duplicating effort across services. Code style varies, infrastructure decisions drift, and teams end up with “snowflake” services that behave and deploy differently — slowing development and creating operational headaches.

This is the critical challenge: Rails is more than just a framework — it’s a system of defaults, guardrails, and integrations. Go, while powerful and fast, doesn’t offer those out of the box.

These are all solvable problems, but they require more up-front investment and process alignment.

How Some Teams Approach This: Introducing Structure with Encore.go

Encore.go

Encore.go is one example of a tool that aims to help teams transition from Rails to Go more smoothly. It brings structure and built-in tooling to Go development, aiming to provide Rails-style productivity while keeping Go's performance and simplicity.

While it won’t be the right fit for every team or project, many who’ve migrated from Rails say it helped them retain familiar conventions and reduce the amount of foundational work necessary to become productive using Go.

Encore includes:

  • Built-in functionality: Auth, routing, database migrations, service-to-service connection handling, etc.
  • Convention-driven development: Conventions for defining APIs, services, testing, integrating infrastructure, etc.
  • No configuration: One command to start the local environment, including all infrastructure.
  • Observability: Tracing, metrics, and structured logging are included by default and available in Encore's development dashboard.
  • Cloud-native design: Support for Docker or automatic deployment to AWS/GCP via Encore Cloud.

Rather than starting from scratch with raw Go, some teams use Encore as a way to adopt Go incrementally — without giving up the structure they’re used to.

Here’s what that can look like in practice:

Code Examples

Defining a service

Encore recognizes a Go package as a service. When running your app or deploying, it automatically provisions the required infrastructure and generates the connection boilerplate.

/my-app ├── encore.app // ... and other top-level project files │ ├── hello // hello service (a Go package) │   ├── hello.go // hello service code │   └── hello_test.go // tests for hello service │ └── world // world service (a Go package) └── world.go // world service code

See the app structure documentation for more details.

Defining APIs

Using the //encore:api annotation, you define an API endpoint in pure Go:

package hello // service name //encore:api public func Ping(ctx context.Context, params *PingParams) (*PingResponse, error) { msg := fmt.Sprintf("Hello, %s!", params.Name) return &PingResponse{Message: msg}, nil }

Encore then generates the required boilerplate and documentation automatically.

Learn more in the docs for defining APIs.

Running locally

Just run:

encore run

Encore will automatically set up the local infrastructure and a local development dashboard with API docs, architecture diagrams, tracing, and logs.

Cloud Deployment

Use Encore’s open-source tooling to build Docker images, or opt into Encore Cloud for automated infrastructure provisioning and deployment to your cloud on AWS or GCP.

Lessons from Teams Who Made the Move

Bookshop.org: Bringing Order to Go Migration

Bookshop.org

Bookshop.org started their migration from Rails to Go to improve scalability and reliability. But the process proved more difficult than expected due to a lack of strong conventions in Go development, and extensive infrastructure overhead when deploying new services.

"We knew that we wanted to start moving some of our more complex systems over to Go, but coming from Rails, we found that we didn’t have the conventions we were used to, and that created inconsistency in our applications. Over time, we were frustrated with deployments. While Go produces a single binary, it was still a lot of Terraform and bespoke deployment work. Secrets, environment variables—everything was all over the place."

Mason Stewart, CTO at Bookshop.org

Bookshop.org adopted Encore to bring consistency and reduce DevOps overhead. It wasn’t just about code, it was about restoring productivity for a team used to working fast.

"We would never have been able to build our ebooks product in the time we did without Encore. It provided exactly the clarity, tooling, and stability we needed. And as we migrate more services to Encore.go, we're on track to save over $60,000 per year compared to our legacy Rails application."

Mason Stewart, CTO at Bookshop.org

Read the full case study →

Quiqup: Scaling Beyond Rails

Quiqup

Quiqup, a logistics platform, hit performance limits with their monolithic Rails app. They decided they wanted to use Go for its performance, but didn’t want to go full microservices and needed more modularity and productivity.

"I loved the modular monolith concept of Encore. Microservices are too heavy to manage without a big team, and monoliths have their own issues. Encore was the perfect balance."

Danny Hawkins, CTO at Quiqup

Quiqup used Encore to gain the performance and reliability of Go, while keeping the speed and simplicity they were used to from Rails.

As they rearchitected their platform, they organized the system into three core domains: fulfillment, logistics, and order channels. Each domain was developed as a set of focused services. Encore made it easy to define and deploy these services with minimal boilerplate by providing clear conventions and built-in tooling that kept development fast and consistent.

Encore's automatic infrastructure provisioning removed the need for a dedicated DevOps team. Every new service came with structured observability, routing, and configuration already in place, which allowed the team to focus on product logic instead of setting up CI pipelines or managing cloud environments.

Their current architecture now includes:

  • Over 200 API endpoints
  • More than 30 independently deployable services
  • All with no dedicated DevOps engineers

By standardizing how services are defined, connected, and deployed, Encore helped Quiqup scale their engineering efforts without introducing the usual complexity of microservices. Rather than slowing down as the system grew, the team maintained the speed they were used to from Rails, now paired with the performance benefits of Go.

"We’ve built our platform very quickly. It just wouldn’t have been possible without Encore. If we were onboarding people onto Go without Encore, it would have taken 2–3x longer to get productive."

Danny Hawkins, CTO at Quiqup

Read the full case study →

Final Thoughts: Migrating from Rails to Go

Go is a great fit for performance-critical systems, but migrating from Rails normally means giving up a lot of built-in tooling and shared conventions. That transition can be jarring.

The good news: you don’t have to reinvent everything.

Frameworks like Encore.go are one way teams have approached this transition, helping them keep the speed of Rails while gaining the performance and flexibility of Go.

Whether you adopt Encore, build your own internal framework, or go fully DIY: the key is recognizing the gap and planning for how you’ll bridge it.

Thinking about moving to Go?
We’re happy to chat and share what we’ve learned from helping other teams make the switch. Book a 1:1 intro — no pressure, just a conversation.

Encore

This blog is presented by Encore, the backend framework for building robust type-safe distributed systems with declarative infrastructure.

Like this article?
Get future ones straight to your mailbox.

You can unsubscribe at any time.