Building a Scalable SaaS with Rails, React, and Terraform
Creating a SaaS product that can grow from a handful of users to thousands requires a solid foundation. In this article I share the architecture and workflow we use at developerz.ai to deliver reliable, production-grade software quickly.
1. Choose the right stack
We start with Rails for the API because it provides convention over configuration, a rich ecosystem, and built-in security features. For the front-end we use React with functional components and hooks, which lets us build interactive UIs without a heavy framework overhead.
2. Separate concerns with micro-services
Even though Rails can handle everything, we isolate long-running jobs into a Sidekiq worker service. This keeps the API responsive and lets us scale workers independently. The worker service runs in its own Docker container and connects to the same PostgreSQL database.
3. Infrastructure as code
All cloud resources are defined with Terraform. We keep a module for each component:
-
postgresql- creates the database instance, backups, and monitoring alarms. -
ecs_service- defines the container service, load balancer, and autoscaling policies. -
s3_bucket- stores static assets and user uploads.
Each module receives variables for environment-specific values, making it easy to spin up a staging environment that mirrors production.
module "postgresql" {
source = "./modules/postgresql"
env = var.environment
db_name = "${var.project}_db"
username = var.db_user
password = var.db_password
}
Running terraform apply creates or updates the entire stack in a single, repeatable step.
4. Continuous integration and deployment
Our CI pipeline runs on GitHub Actions. The workflow consists of:
- Linting JavaScript with
eslintand Ruby withrubocop. - Running unit tests for both the front-end and back-end.
- Building a Docker image and pushing it to a private registry.
- Deploying the new image to the ECS service using a Terraform
null_resourcethat triggers a rolling update.
Because the pipeline is fully automated, a merge to main results in a new version being live within minutes.
5. Feature flags for safe releases
We use LaunchDarkly to control feature rollout. Each new feature is wrapped in a flag that defaults to false. After deployment we enable the flag for internal users, monitor metrics, and then gradually expand the audience.
if LaunchDarkly.enabled?("new_dashboard", current_user)
render "dashboard_new"
else
render "dashboard_legacy"
end
If a problem appears, flipping the flag off instantly reverts the change without a new deployment.
6. Monitoring and observability
All services emit structured logs to CloudWatch and expose Prometheus metrics. We set up alerts for latency spikes, error rates, and resource exhaustion. A simple Grafana dashboard shows the health of the API, workers, and database.
7. Lessons learned
- Write reversible migrations. They save time when you need to roll back a change.
- Keep Docker images small by using multi-stage builds. Smaller images start faster and reduce attack surface.
- Automate everything from linting to infrastructure provisioning. Manual steps are the biggest source of errors.
- Feature flags are not a substitute for good testing, but they provide a safety net for production releases.
8. Closing thoughts
Building a SaaS product that scales is less about flashy tech and more about disciplined engineering practices. By combining Rails, React, Terraform, and a robust CI/CD pipeline we deliver new features quickly while maintaining stability. If you are a technical founder looking for a partner who can turn your idea into a production-grade service, feel free to reach out.
Top comments (0)