Oct 28, 20267 min read

How to Move a Go API from Render to AWS

Moving a Go web service, Render Postgres, environment variables and a Render cron job from render.yaml to your own AWS account.

Render is an easy home for a Go API: a render.yaml Blueprint with a web service that runs ./orders, a Render Postgres database whose connection string lands in DATABASE_URL, environment variables for keys, and a cron job service for the hourly cleanup. It starts to pinch when a customer asks for the data to live in your own AWS account, when the database outgrows its instance type, or when the rest of the company already runs on AWS.

This guide moves a small Go orders API off Render. The app has a web service, Render Postgres, two environment variables and an hourly cron job that cancels stale orders. It covers the mapping, a tested Encore version of the service, the manual AWS path, the data move and the DNS switch.

Render to AWS mapping at a glance

RenderEncore on AWSManual AWS
Web serviceEncore service, built and run on FargateECS service, task definition, load balancer
Render Postgressqldb.NewDatabase, provisioned as RDSRDS instance, subnet group, security groups
Environment variablesEncore secrets in Secrets ManagerSecrets Manager plus task definition entries
Cron job servicecron.NewJob calling an endpointEventBridge Scheduler and a task or Lambda
preDeployCommandMigrations applied on deployA one-off ECS task before the update
Preview environmentsPreview environments per pull requestSeparate stacks per branch

Encore: the Render pieces as declarations in the Go code

Encore combines an open source infrastructure SDK for Go and TypeScript with a platform that deploys into your own AWS or GCP account. The services and databases listed in render.yaml become declarations in the Go service. The database replaces Render Postgres, and the hourly job replaces the cron job service:

// Service orders was the web process on the old platform. package orders import ( "context" "errors" "time" "encore.dev/beta/errs" "encore.dev/cron" "encore.dev/storage/sqldb" ) // Replaces Render Postgres: RDS on AWS, local Postgres under `encore run`. var db = sqldb.NewDatabase("orders", sqldb.DatabaseConfig{ Migrations: "./migrations", }) // Replaces the scheduled cleanup task on the old platform. var _ = cron.NewJob("expire-pending", cron.JobConfig{ Title: "Cancel orders left pending for a day", Every: 1 * cron.Hour, Endpoint: ExpirePending, }) type Order struct { ID int64 `json:"id"` Customer string `json:"customer"` TotalCents int64 `json:"totalCents"` Status string `json:"status"` CreatedAt time.Time `json:"createdAt"` } type CreateParams struct { Customer string `json:"customer"` TotalCents int64 `json:"totalCents"` } //encore:api public method=POST path=/orders func Create(ctx context.Context, p *CreateParams) (*Order, error) { if p.Customer == "" || p.TotalCents <= 0 { return nil, &errs.Error{Code: errs.InvalidArgument, Message: "customer and totalCents are required"} } o := &Order{} err := db.QueryRow(ctx, ` INSERT INTO orders (customer, total_cents) VALUES ($1, $2) RETURNING id, customer, total_cents, status, created_at`, p.Customer, p.TotalCents, ).Scan(&o.ID, &o.Customer, &o.TotalCents, &o.Status, &o.CreatedAt) return o, err } //encore:api public method=GET path=/orders/:id func Get(ctx context.Context, id int64) (*Order, error) { o := &Order{} err := db.QueryRow(ctx, ` SELECT id, customer, total_cents, status, created_at FROM orders WHERE id = $1`, id, ).Scan(&o.ID, &o.Customer, &o.TotalCents, &o.Status, &o.CreatedAt) if errors.Is(err, sqldb.ErrNoRows) { return nil, &errs.Error{Code: errs.NotFound, Message: "order not found"} } return o, err } type ExpireResult struct { Cancelled int64 `json:"cancelled"` } // ExpirePending is safe to run twice: it only touches rows still pending. // //encore:api private method=POST path=/orders/expire-pending func ExpirePending(ctx context.Context) (*ExpireResult, error) { res, err := db.Exec(ctx, ` UPDATE orders SET status = 'cancelled' WHERE status = 'pending' AND created_at < now() - interval '24 hours'`) if err != nil { return nil, err } return &ExpireResult{Cancelled: res.RowsAffected()}, nil }

The migration in orders/migrations/1_create_orders.up.sql matches the table that already exists on Render, which is what lets the data be restored into it later:

CREATE TABLE orders ( id BIGSERIAL PRIMARY KEY, customer TEXT NOT NULL, total_cents BIGINT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TIMESTAMPTZ NOT NULL DEFAULT now() );

Environment variables that hold credentials become secrets, declared as fields on a var secrets struct in the service that uses them and set with encore secret set --type production. Values that are not secret can stay as constants in the code. encore run starts Postgres locally and applies the migration, which replaces pointing a laptop at a shared Render database:

~/orders
$ encore run$ curl -X POST localhost:4000/orders -d '{"customer":"initech","totalCents":1200}'{"id":3,"customer":"initech","totalCents":1200,"status":"pending","createdAt":"2026-10-28T12:51:11Z"}$ curl -X POST localhost:4000/orders -d '{"customer":""}'{"code":"invalid_argument","message":"customer and totalCents are required","details":null}$ curl -X POST localhost:4000/orders/expire-pending{"cancelled":1}

Cron jobs do not run locally, so the cleanup endpoint is called by hand above. After connecting your AWS account and pushing, Encore provisions RDS, the secrets in Secrets Manager, the scheduled job and the compute in your own account, and provisions an IAM role for the service. Migrations run as part of each deploy, which covers what preDeployCommand usually did on Render. The infrastructure docs describe what each declaration becomes in AWS.

For a team that writes code with AI agents, the Render dashboard settings turn into code an agent can read and change in a pull request, and the agent can run the result with encore run before anyone deploys it.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

Manual path: ECS on Fargate and RDS

Without Encore, the Go binary stays the same and reads DATABASE_URL and the API key from environment variables. A non-root distroless image replaces Render's native Go build:

FROM golang:1.25 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o /orders ./cmd/orders FROM gcr.io/distroless/static-debian12:nonroot COPY --from=build /orders /orders EXPOSE 8080 ENTRYPOINT ["/orders"]

Render sets PORT for the web service, and on ECS you choose the container port yourself, so read it from configuration with a default. Then come the AWS resources: an RDS instance in private subnets, an ECR repository, an ECS cluster and service behind an Application Load Balancer, Secrets Manager entries for each credential referenced from the task definition, and an execution role that can read them. The hourly job becomes an EventBridge Scheduler schedule that runs an ECS task with a different command, which needs its own IAM role. Most teams write this in Terraform so staging can be a second copy.

Good to know

Render's external database URL requires TLS and so does RDS by default, so keep sslmode=require in the connection string. If the app ran migrations from preDeployCommand, run them as a one-off ECS task before the service updates.

Moving the data

Stop writes by suspending the Render web service, then dump the data through the external database URL from the Render Dashboard with pg_dump -Fc --data-only --no-owner, which writes the custom format that pg_restore reads. With Encore the schema already exists from the migration, so restore only the data through a temporary connection string:

~/orders
$ encore db conn-uri orders --env=prod --write$ pg_restore --data-only --no-owner \ --dbname "$(encore db conn-uri orders --env=prod --write)" orders.dump$ curl https://prod-orders-xxxx.encr.app/orders/1{"id":1,"customer":"acme","totalCents":1999,"status":"paid","createdAt":"2026-10-25T11:51:10Z"}

--data-only also restores the current value of the orders_id_seq sequence, so new orders continue from the highest id. Check row counts on a few tables before switching traffic. For the manual path, restore into RDS from a host inside the VPC, such as CloudShell in a VPC environment, since the instance is not publicly reachable.

Switching DNS

Add the custom domain to the new environment, wait for its certificate, then point the DNS record away from Render. Keep the Render database for a few days, so you can switch back if something was missed, and suspend the cron job service there so it does not run against the old data.

How to choose

Pick Encore when you want the Render experience of pushing code and getting a running app, with the database, secrets and scheduled jobs defined in the Go code and running in your own AWS account, plus preview environments for each pull request. The same service can add Pub/Sub for work that used to run on a Render background worker.

Pick the manual ECS path when your team already runs Terraform for other AWS services and wants this app to use the same modules.

Frequently asked questions

How do I migrate a Go app from Render to AWS?

Map each service in render.yaml to an AWS one: the web service to a container service, Render Postgres to RDS, environment variables to Secrets Manager, and the cron job service to a scheduled job. Then copy the data with pg_dump and pg_restore during a short write freeze and switch DNS. With Encore, the database, secrets and cron jobs are declared in the Go code and provisioned in your AWS account on deploy.

How do I copy Render Postgres into RDS?

Use the external database URL from the Render Dashboard, dump with pg_dump in the custom format, and restore into the new database with pg_restore. Create the schema with your migrations first and restore with --data-only, or restore schema and data together into an empty database.

What replaces a Render cron job on AWS?

EventBridge Scheduler triggering an ECS task or a Lambda function on a cron or rate expression.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders