how to deploy a go backend that lives inside a turborepo monorepo — build cmd, openapi path, gin mode, all the gotchas
when creating a new web service on render:
repo → select your github repo
branch → main
root dir → . (leave as repo root, never change this for monorepos)
runtime → Go (render defaults to Node because it sees package.json — change it)never set root dir to apps/backend — render needs to see the full monorepo to run commands across packages.
depends on where your main.go lives:
# if main.go is at apps/backend/cmd/server/main.go
cd packages/openapi && bun install && bun run generate && cp openapi.json ../../apps/backend/openapi.json && cd ../../apps/backend && go build -o main ./cmd/server
# if main.go is at apps/backend/cmd/main.go
cd packages/openapi && bun install && bun run generate && cp openapi.json ../../apps/backend/openapi.json && cd ../../apps/backend && go build -o main ./cmd
# if main.go is at apps/backend/main.go
cd packages/openapi && bun install && bun run generate && cp openapi.json ../../apps/backend/openapi.json && cd ../../apps/backend && go build -o main .the cp openapi.json step is important — copies the generated file next to the binary so it can find it at runtime.
# same pattern — match where your binary ends up
apps/backend/mainGIN_MODE=release # not "production" — gin specifically uses "release"
PORT=5000 # or whatever port your server listens on
DATABASE_URL= # your neon/postgres connection string
JWT_ACCESS_SECRET= # your secrets
JWT_REFRESH_SECRET=
OPENAPI_PATH= # see belowrender runs your binary from a specific absolute path. check your deploy logs for a line like:
Generated openapi.json at /opt/render/project/go/src/github.com/YourUsername/yourrepo/apps/backend/openapi.jsonor look for where your server starts:
source=/opt/render/project/go/src/github.com/YourUsername/yourrepo/apps/backend/cmd/server/main.goset OPENAPI_PATH to the absolute path of openapi.json in your backend:
OPENAPI_PATH=/opt/render/project/go/src/github.com/YourUsername/yourrepo/apps/backend/openapi.jsonin main.go the fallback looks like this:
openapiPath := os.Getenv("OPENAPI_PATH")
if openapiPath == "" {
openapiPath = filepath.Join("openapi.json") // relative — works locally, breaks on render
}the relative path works locally because you run the binary from the backend folder. on render the working directory is different — always use the absolute path via env var.
render defaulted to Node runtime
render sees package.json in the monorepo root and assumes Node. manually change runtime to Go.
turbo not found during build
/usr/bin/bash: line 1: turbo: command not found
error: script "generate" exited with code 127render doesn't have turbo installed globally. don't use turbo run generate in the build cmd — call the script directly with bun:
cd packages/openapi && bun install && bun run generateopenapi.json returning 500
[GIN] | 500 | GET "/openapi.json"
Document could not be loadedthe file was generated in packages/openapi/ but the binary looked for it relative to its own working directory. fix — copy the file to apps/backend/ during build and set OPENAPI_PATH to the absolute path on render.
set root dir to apps/backend
build cmd failed because cd packages/openapi doesn't exist relative to apps/backend. always leave root dir as ..