Deploy a Go, Express or NestJS backend to your own AWS account with Postgres, queues, cron and secrets declared in application code.
To deploy a Go, Express or NestJS backend to AWS without Terraform, use infrastructure from code: declare the resources in application code and let a deploy system provision them. Encore reads those declarations, provisions Postgres, queues, cron jobs, secrets and object storage in your own AWS account, and deploys the app on push. OpenTofu, Pulumi and AWS CDK remain options when a platform team wants a separate IaC codebase.
This guide puts Encore first, then shows the same Postgres-plus-queue shape for Express, NestJS and Go, compares the main alternatives, and ends with when to keep Terraform.
sst.config.ts, deployed with Pulumi under the hood.The ranking is for application teams that own the backend. A platform team that already runs a large Terraform estate may keep OpenTofu or Pulumi for shared networking and use Encore only for application resources.
Encore reads infrastructure declarations in your TypeScript or Go code with static analysis, runs them locally with encore run, and provisions the matching managed services when you deploy to your own AWS or GCP account. It works with any framework: keep NestJS or Express for structure, or mount Gin, Echo, Chi and net/http handlers in Go as raw endpoints.
Josef Sima, Engineering Director at Groupon, said on the customers page: "Encore is our foundation for all new development. What used to take days or weeks between developers and infra teams now takes minutes."
Run in your terminal to get started locally.
Connect AWS in the Encore dashboard under Cloud, grant the provisioning IAM role the wizard creates, then push to the branch configured for an environment.

Instance sizes, networking and backups live in environment configuration, outside the application code.
Encore works alongside Express. You can wrap an existing router in a raw endpoint while you move handlers over, or declare new APIs with Encore and add infrastructure next to them. A small orders service with Postgres and a queue looks like this:
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { Topic, Subscription } from "encore.dev/pubsub";
import { secret } from "encore.dev/config";
const db = new SQLDatabase("orders", { migrations: "./migrations" });
const stripeKey = secret("StripeKey");
interface OrderPlaced {
orderId: string;
}
export const orderPlaced = new Topic<OrderPlaced>("order-placed", {
deliveryGuarantee: "at-least-once",
});
const _ = new Subscription(orderPlaced, "fulfill", {
handler: async (event) => {
// fulfill using stripeKey() and the order row
void event.orderId;
void stripeKey;
},
});
export const create = api(
{ expose: true, method: "POST", path: "/orders" },
async (req: { total: number }): Promise<{ id: string }> => {
const row = await db.queryRow<{ id: string }>`
INSERT INTO orders (total) VALUES (${req.total})
RETURNING id::text AS id`;
await orderPlaced.publish({ orderId: row!.id });
return { id: row!.id };
},
);
On AWS that maps to RDS PostgreSQL, SNS with SQS and Secrets Manager, and Encore provisions an IAM role for the service. See the Express migration guide for a forklift path that keeps your existing router during the move.
Keep Nest modules, providers and dependency injection, add encore.dev, mark the directory with encore.service.ts, start Nest as a standalone application context, and expose Nest services through Encore api endpoints. Declare the database and a cron job next to the Nest providers:
import { Service } from "encore.dev/service";
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { CronJob } from "encore.dev/cron";
import { Bucket } from "encore.dev/storage/objects";
export default new Service("nest");
const db = new SQLDatabase("appDB", { migrations: "./migrations" });
const receipts = new Bucket("receipts", { versioned: false });
export const cleanup = api({}, async () => {
await db.exec`
DELETE FROM orders
WHERE completed = true
AND created_at < NOW() - INTERVAL '30 days'`;
});
const _ = new CronJob("cleanup-orders", {
title: "Clean up old orders",
every: "24h",
endpoint: cleanup,
});
export const uploadReceipt = api(
{ expose: false, method: "POST", path: "/receipts/:orderId" },
async (req: { orderId: string; body: string }): Promise<{ key: string }> => {
const key = `${req.orderId}.json`;
await receipts.upload(key, Buffer.from(req.body));
return { key };
},
);
uploadReceipt stays private, so /receipts/:orderId is not a public route. placeOrder requires auth, creates the order id itself, and uploads a receipt built from that order through the generated client, the same service-to-service call Encore uses between services:
import { Service } from "encore.dev/service";
import { api } from "encore.dev/api";
import { nest } from "~encore/clients";
export default new Service("orders");
export const placeOrder = api(
{ expose: true, auth: true, method: "POST", path: "/orders" },
async (req: { total: number }): Promise<{ key: string }> => {
const orderId = crypto.randomUUID();
const receipt = await nest.uploadReceipt({
orderId,
body: JSON.stringify({ orderId, total: req.total }),
});
return receipt;
},
);
encore run starts Postgres locally and runs migrations. A deploy provisions RDS, an S3 bucket, and the services on Fargate or Kubernetes in your account. Full walkthrough: Deploy NestJS to AWS.
In Go the same shape uses sqldb, pubsub and cron packages:
package orders
import (
"context"
"encore.dev/cron"
"encore.dev/pubsub"
"encore.dev/storage/sqldb"
)
var db = sqldb.NewDatabase("orders", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
type OrderPlaced struct {
OrderID string
}
var OrderPlacedTopic = pubsub.NewTopic[*OrderPlaced]("order-placed", pubsub.TopicConfig{
DeliveryGuarantee: pubsub.AtLeastOnce,
})
type CreateParams struct {
Total int `json:"total"`
}
type CreateResponse struct {
ID string `json:"id"`
}
//encore:api public method=POST path=/orders
func Create(ctx context.Context, p *CreateParams) (*CreateResponse, error) {
var id string
err := db.QueryRow(ctx, `
INSERT INTO orders (total) VALUES ($1) RETURNING id::text
`, p.Total).Scan(&id)
if err != nil {
return nil, err
}
if _, err := OrderPlacedTopic.Publish(ctx, &OrderPlaced{OrderID: id}); err != nil {
return nil, err
}
return &CreateResponse{ID: id}, nil
}
//encore:api private
func Cleanup(ctx context.Context) error {
_, err := db.Exec(ctx, `
DELETE FROM orders
WHERE completed = true
AND created_at < NOW() - INTERVAL '30 days'
`)
return err
}
var _ = cron.NewJob("cleanup-orders", cron.JobConfig{
Title: "Clean up old orders",
Every: 24 * cron.Hour,
Endpoint: Cleanup,
})
Existing Gin or Echo routers can mount as raw endpoints while you migrate service by service.
From the declarations above, a typical AWS environment includes:
encore secret set, stored in AWS Secrets Manager and injected at runtimeThe resources are standard managed services in your account that you can see in the AWS console, not a vendor-hosted runtime for production.
Yes. Keep Terraform for shared and org-level infrastructure, and let Encore own the application resources it supports. The Encore Terraform provider exposes provisioned resources as data sources, and existing databases such as RDS can be connected rather than recreated. The migration guide walks through an incremental path.
encore build docker --config=infra-config.json orders:latest
You bring Postgres, Redis, Pub/Sub (SNS+SQS, GCP Pub/Sub or NSQ), S3-compatible storage and metrics. The image runs your services. It does not run migrations or trigger cron jobs. You apply migrations yourself and call cron endpoints from your own scheduler. Details: Build Docker images and configure infrastructure.
Use a tool that generates the AWS resources for you. Infrastructure from code tools read resource declarations in your application. AWS CDK, Pulumi and SST let you describe infrastructure in TypeScript or Go instead of HCL. SAM fits Lambda-first services.
Yes. Encore provisions into your own AWS or GCP account through a role you grant and can revoke (on GCP, a service account). Resources stay in your account. You can also self-host with encore build docker and bring your own Postgres, Redis, Pub/Sub, storage and metrics.
Keep Terraform or OpenTofu for org-level and shared infrastructure, such as account structure, custom networking, specialized AWS services and company-wide IAM boundaries. Application-level resources are the part that most often moves to a higher-level tool.
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.