DEV Community

Cover image for Deploying a Full-Stack Monorepo (Go + React) to Sevalla: What I Learned
K Srinivas Rao
K Srinivas Rao

Posted on

Deploying a Full-Stack Monorepo (Go + React) to Sevalla: What I Learned

I recently deployed a task management app to Sevalla. The app has a React frontend, Go backend, PostgreSQL database, Redis for background job processing, and cron jobs. All of this lives in a single monorepo.

You can find the code here https://github.com/sriniously/tasker

And also the walkthrough video at https://www.youtube.com/watch?v=wdoIff5GjFc

Here's how I deployed and what I learned during the process.

Why I Chose Sevalla

I evaluated several deployment platforms before settling on Sevalla. The main factors were the integrated services and the flexibility to handles monorepos.

Sevalla provides managed PostgreSQL and Redis instances. This eliminates the need to configure databases myself. The platform handles backups, updates, and scaling automatically.

Internal networking between services was another key feature. The backend can connect to databases through private networking, which improves security and performance.

The platform includes S3-compatible object storage. This meant I could use existing AWS SDK code without modifications.

The pricing model is straightforward with no surprise charges. Resources scale based on usage, and you pay only for what you consume. No seat based pricing.

The Monorepo Challenge

My repository structure looks like this:

.
├── apps
│   ├── backend
│   │   ├── cmd
│   │   │   ├── cron/
│   │   │   └── tasker/
│   │   ├── internal
│   │   │   ├── config/
│   │   │   ├── cron/
│   │   │   ├── database/
│   │   │   │   └── migrations/
│   │   │   ├── handler/
│   │   │   ├── lib/
│   │   │   │   ├── aws/
│   │   │   │   ├── email/
│   │   │   │   ├── job/
│   │   │   │   └── utils/
│   │   │   ├── middleware/
│   │   │   ├── model/
│   │   │   │   ├── category/
│   │   │   │   ├── comment/
│   │   │   │   └── todo/
│   │   │   ├── repository/
│   │   │   ├── router/
│   │   │   │   └── v1/
│   │   │   ├── service/
│   │   │   └── testing/
│   │   ├── static/
│   │   ├── templates/
│   │   │   └── emails/
│   │   ├── go.mod
│   │   └── README.md
│   └── frontend
│       ├── src
│       │   ├── api/
│       │   │   └── hooks/
│       │   ├── components/
│       │   │   ├── categories/
│       │   │   ├── layouts/
│       │   │   ├── todos/
│       │   │   └── ui/
│       │   ├── pages/
│       │   └── lib/
│       ├── package.json
│       └── vite.config.ts
├── packages
│   ├── emails/
│   │   └── src/templates/
│   ├── openapi/
│   │   └── src/contracts/
│   └── zod/
│       └── src/
├── package.json
├── turbo.json
└── README.md
Enter fullscreen mode Exit fullscreen mode

The frontend depends on packages in the packages/ directory. These need to be built before the frontend can compile. The backend contains multiple entry points for the API server and cron processors.

Standard deployment platforms don't handle this complexity well. They expect a single build command that produces one deployable artifact. With a monorepo, you need orchestrated builds across multiple directories.

Sevalla uses Nixpacks for builds, which allows custom configuration through nixpacks.toml files. This flexibility was important for handling my monorepo structure.

Setting Up Nixpacks for Monorepos

I created two nixpacks.toml files to handle my frontend and backend builds separately.

Frontend Configuration

The frontend configuration goes in the repository root:

[phases.setup]
nixPkgs = ['nodejs_22', 'bun']
aptPkgs = ['git']

[phases.install]
workDir = '/app'
cmds = [
    'bun install --frozen-lockfile',
    'bun run build --filter=@tasker/openapi --filter=@tasker/zod'
]

[phases.build]
workDir = '/app/apps/frontend'
cmds = ['bun run build']

[start]
cmd = 'cd /app/apps/frontend && bun run start -- -l ${PORT:-3000}'

[variables]
NODE_ENV = 'production'
Enter fullscreen mode Exit fullscreen mode

It builds the OpenAPI client and Zod packages first. Only then does it build the React app. This orchestrated build process ensures that all dependencies are available before building the main application.

Backend Configuration

The backend configuration lives in backend/nixpacks.toml:

[phases.setup]
nixPkgs = ['go']

[phases.install]
cmds = ['go mod download']

[phases.build]
cmds = [
    'go build -o tasker ./cmd/tasker',
    'go build -o cron ./cmd/cron'
]

[start]
cmd = './tasker'

[variables]
GO111MODULE = 'on'
CGO_ENABLED = '0'
Enter fullscreen mode Exit fullscreen mode

This builds both the API server and the cron processor from the same codebase. The API server runs by default, and cron jobs get deployed separately using the cron executable.

Environment Variables in Sevalla

This was the biggest surprise. In production, Sevalla groups environment variables by prefix into maps. Locally, you get individual variables like DATABASE_HOST and DATABASE_USER. In production, variables with the same prefix get grouped together.

Instead of:

DATABASE_HOST=localhost
DATABASE_USER=postgres
DATABASE_PASSWORD=secret
Enter fullscreen mode Exit fullscreen mode

You get:

DATABASE=map[HOST:your-host USER:postgres PASSWORD:secret]
Enter fullscreen mode Exit fullscreen mode

I had to add special handling in my Go config loader to parse these map strings and set individual environment variables that my app expects.

Sevalla's environment variable grouping is designed to simplify configuration management. Instead of dozens of individual variables, you get logical groups. However, this requires adaptation in your application code.

Setting Up Infrastructure Services

Before deploying the application code, I set up the supporting infrastructure through Sevalla's dashboard.

PostgreSQL Database

Creating the database was straightforward:

  • Database name: tasker
  • Version: PostgreSQL 17
  • Instance name: Tasker PG
  • Location: Mumbai (closest to me)
  • Resource tier: Basic

Sevalla generates connection details automatically. I copied the host, port, username, and password for my backend environment variables.

The important part is that the host uses Sevalla's internal networking. This means it's only accessible from other services within the same project.

Redis Setup

Similar process for Redis:

  • Version: 7
  • Instance name: Tasker Redis
  • Auto-generated password
  • Same location as the database

Unlike local development where Redis runs without authentication, Sevalla's Redis instances come with passwords by default. I had to update my Redis client configuration to handle this.

Object Storage

I created an object storage:

  • Instance name: tasker
  • Same region as other resources
  • S3-compatible API

Sevalla's object storage works with existing AWS SDK code without modifications.

Deploying the Backend

The backend deployment required specific configuration for the monorepo structure.

I created a new app in Sevalla with these settings:

The build path setting is important for monorepo deployments. It tells Sevalla to look for the nixpacks.toml file in the backend directory rather than the repository root.

Environment Variables

I imported my local .env file and updated values for production:

One important detail: remove quotes from environment variable values in the Sevalla dashboard.

Internal Connections

For security, I connected the backend to the database and Redis using internal networking. You add these connections in the backend app's dashboard under "Connections."

This provides several benefits:

  • No external database access
  • Better performance through internal routing
  • Automatic service discovery

Deploying the Frontend

The frontend deployment was more straightforward since it uses the root build configuration.

I created another app:

  • Same repository
  • Root build path
  • Application name: Tasker Web
  • Same location

The main environment variables were:

VITE_CLERK_PUBLISHABLE_KEY=your-clerk-key
VITE_API_URL=https://your-backend-url
Enter fullscreen mode Exit fullscreen mode

I had to deploy the backend first to get its URL for the frontend configuration.

Setting Up Cron Jobs

My app sends weekly reports via email. This required setting up a cron process in Sevalla.

In the backend app dashboard, I added a cron job:

  • Name: weekly-reports
  • Command: ./cron weekly-reports
  • Schedule: 0 0 * * 0
  • Instance size: hobby tier

The cron executable was built during the backend build process. I could test it manually using the "Run Now" button before the scheduled time.

The schedule uses standard cron syntax, and the timezone follows UTC by default.

Testing the Deployment

After deployment, I tested each part of the system:

Authentication: Signed in with Google through Clerk. This verified the frontend could communicate with Clerk's services.

Database Operations: Created categories and tasks. This tested the backend API and PostgreSQL integration.

File Uploads: Uploaded a PDF attachment. This verified object storage integration and presigned URL generation.

Cron Jobs: Triggered the weekly report manually. I received the email with correct task statistics.

Internal Networking: Verified that the backend could connect to PostgreSQL and Redis through internal hostnames.

Sevalla-Specific Features I Found Useful

Database Studio: Sevalla provides a built-in database management interface. I could query the production database directly from the dashboard without external tools.

Automatic SSL: All services got HTTPS automatically without any certificate management.

Deployment Logs: Real-time build and deployment logs made debugging configuration issues straightforward.

Resource Monitoring: Built-in monitoring shows CPU, memory, and network usage for each service.

One-Click Scaling: I could upgrade resource tiers with a single click when needed.

Problems I Encountered

Build Path Configuration: It took me time to figure out that I needed to set the build path to ./apps/backend for the backend deployment to find the right Nixpacks file.

Environment Variable Grouping: The different behavior between local and production environment variable loading required code changes.

Redis Authentication: I initially forgot that production Redis instances have passwords, which caused connection failures.

CORS Configuration: I had to update the allowed origins to include the production frontend URL after deployment.

What Worked Well About Sevalla

Integrated Services: Having databases, object storage, and application hosting in one platform simplified management significantly.

Internal Networking: Connecting services internally was both secure and performant. No need to manage external database URLs or worry about unauthorized access.

Monorepo Support: The Nixpacks configuration system handled my complex build requirements well.

Deployment Speed: Once configured, deployments were fast and reliable.

Resource Flexibility: Starting with basic tiers and upgrading as needed was cost-effective.

Sevalla vs Other Platforms

Compared to other platforms I've used:

Easier Database Management: Unlike Heroku where you need add-ons, or AWS where you configure RDS separately, Sevalla's integrated databases are simpler to set up and manage.

Simpler Networking: Internal service communication is more straightforward than configuring VPCs on AWS or managing service discovery on Kubernetes.

Cost Predictability: The pricing model is clearer than AWS's complex billing or Heroku's expensive add-ons.

Key Takeaways for Sevalla Deployments

Plan for Environment Variables: If you're migrating from another platform, expect to modify how your app handles environment variable loading.

Use Internal Networking: Take advantage of Sevalla's internal networking for service-to-service communication. It's more secure and performant than external connections.

Start Small and Scale: Begin with basic resource tiers. Sevalla makes it easy to upgrade when you need more capacity.

Leverage Integrated Services: Use Sevalla's managed databases and object storage instead of external services. The integration simplifies configuration and management.

Test Cron Jobs: Use the manual trigger feature to test background jobs before relying on scheduled execution.

Monitor Resource Usage: Keep an eye on the built-in monitoring to understand when you need to scale resources.

Final Thoughts on Sevalla

Sevalla made deploying a complex monorepo application much more manageable than I expected. The platform handles most of the infrastructure complexity while giving you control over the important configuration details.

The key is understanding how Sevalla differs from local development and preparing for those differences in your code and configuration. The environment variable grouping and internal networking are the biggest changes from typical development setups.

For teams looking to deploy full-stack applications without managing infrastructure complexity, Sevalla provides a good middle ground between platform-as-a-service simplicity and infrastructure-as-a-service control.

The integrated approach works particularly well for monorepos where you need to coordinate multiple services and databases. Having everything in one platform simplifies both deployment and ongoing management.

Ready to Try Sevalla?

If you're working on a full-stack application and tired of juggling multiple services across different platforms, I recommend giving Sevalla a try. The platform is particularly well-suited for:

  • Monorepo applications where you need coordinated builds across multiple services
  • Teams that want managed databases without the complexity of cloud provider setup
  • Applications requiring internal networking between services for security and performance
  • Projects that need both web apps and background jobs running from the same codebase
  • Developers who prefer integrated tooling over managing separate services

The $50 joining credits enough to deploy and test real applications. You can get started with your GitHub repository and have a production deployment running in under an hour.

Check out Sevalla at sevalla.com and see how it handles your specific deployment needs. The platform continues to evolve with new features, and the development team is responsive to user feedback.

Top comments (0)