# Coming from Terraform

> How Terraform concepts map to Encore

Terraform configuration describes the desired infrastructure and its
provider-specific settings. In
Encore, the TypeScript or Go SDK declarations describe the APIs, services, and
infrastructure used by the backend. They form an [application
model](/docs/application-model), while deployment-specific
properties are configured separately for each environment.

For migration paths that also apply to Pulumi, OpenTofu, and AWS CDK, see [Coming from an IaC tool](/docs/platform/migration/from-iac).

## How it works

The following examples declare a Postgres database in Terraform and Encore.

```hcl
resource "aws_db_instance" "site" {
  identifier              = "site"
  engine                  = "postgres"
  instance_class          = "db.t4g.medium"
  allocated_storage       = 20
  storage_type            = "gp3"
  db_name                 = "site"
  username                = var.db_username
  password                = var.db_password
  db_subnet_group_name    = aws_db_subnet_group.main.name
  vpc_security_group_ids  = [aws_security_group.db.id]
  backup_retention_period = 7
}
```

**Encore.ts:**

```ts
import { SQLDatabase } from "encore.dev/storage/sqldb";

const siteDB = new SQLDatabase("site", {
  migrations: "./migrations",
});
```

**Encore.go:**

```go
import "encore.dev/storage/sqldb"

var siteDB = sqldb.NewDatabase("site", sqldb.DatabaseConfig{
    Migrations: "./migrations",
})
```

The Terraform example defines the AWS implementation and its deployment
properties. The Encore declaration defines the logical database name and its
migration directory. Encore Cloud configures the managed database separately
for each environment through [infrastructure configuration](/docs/platform/infrastructure/configuration).

## Where each setting is configured

Configuration is divided across three locations:

| Place | Holds | How you set it |
|---|---|---|
| **Code** | Logical resources and application-level properties | Arguments to resource constructors in the application |
| **Encore Cloud configuration** | Deployment properties for each environment | The [Encore Cloud dashboard](https://app.encore.dev) and, where supported, the cloud provider console |
| **Infra config** | Resource mappings and runtime connectivity for self-hosted deployments | An [`infra.config.json`](/docs/self-host/configure-infra) file passed to `encore build docker --config` |

The infra config file applies to self-hosted deployments. Encore Cloud manages
the corresponding configuration for environments deployed through the platform.

For a SQL database:

| Setting | Place |
|---|---|
| Database name, migrations directory | Code |
| Cloud provider, database engine, and capacity | Encore Cloud configuration |
| Storage, backups, and networking | Encore Cloud configuration or cloud provider console |
| Host, credentials, and TLS | Infra config for self-hosted deployments |

## Converting Terraform resources

### Databases

```hcl
resource "aws_db_instance" "site" { ... }
```

**Encore.ts:**

```ts
import { SQLDatabase } from "encore.dev/storage/sqldb";

const SiteDB = new SQLDatabase("site", { migrations: "./migrations" });
```

**Encore.go:**

```go
import "encore.dev/storage/sqldb"

var SiteDB = sqldb.NewDatabase("site", sqldb.DatabaseConfig{Migrations: "./migrations"})
```

**Encore.ts:**

Schema changes go in `./migrations` as numbered SQL files, applied on deploy.
See [Databases](/docs/ts/primitives/databases).

**Encore.go:**

Schema changes go in `./migrations` as numbered SQL files, applied on deploy.
See [Databases](/docs/go/primitives/databases).

### Pub/Sub topics and subscriptions

A topic, its queue, the subscription, and the queue policy granting the topic
write access:

```hcl
resource "aws_sns_topic" "site_added" { name = "site-added" }
resource "aws_sqs_queue" "check_site" { name = "check-site" }

resource "aws_sns_topic_subscription" "check_site" {
  topic_arn = aws_sns_topic.site_added.arn
  protocol  = "sqs"
  endpoint  = aws_sqs_queue.check_site.arn
}

resource "aws_sqs_queue_policy" "check_site" { ... }
```

**Encore.ts:**

```ts
import { Topic, Subscription } from "encore.dev/pubsub";

export const SiteAdded = new Topic<Site>("site-added", {
  deliveryGuarantee: "at-least-once",
});

const _ = new Subscription(SiteAdded, "check-site", { handler: doCheck });
```

**Encore.go:**

```go
import "encore.dev/pubsub"

var SiteAdded = pubsub.NewTopic[*Site]("site-added", pubsub.TopicConfig{
    DeliveryGuarantee: pubsub.AtLeastOnce,
})

var _ = pubsub.NewSubscription(SiteAdded, "check-site",
    pubsub.SubscriptionConfig[*Site]{Handler: DoCheck},
)
```

**Encore.ts:**

Encore derives the queue, the subscription wiring and the IAM policy from those
two declarations. See [Pub/Sub](/docs/ts/primitives/pubsub).

**Encore.go:**

Encore derives the queue, the subscription wiring and the IAM policy from those
two declarations. See [Pub/Sub](/docs/go/primitives/pubsub).

### Object storage

```hcl
resource "aws_s3_bucket" "profile_pictures" { bucket = "profile-pictures" }
resource "aws_s3_bucket_versioning" "profile_pictures" { ... }
```

**Encore.ts:**

```ts
import { Bucket } from "encore.dev/storage/objects";

export const profilePictures = new Bucket("profile-pictures", {
  versioned: false,
});
```

**Encore.go:**

```go
import "encore.dev/storage/objects"

var ProfilePictures = objects.NewBucket("profile-pictures", objects.BucketConfig{
    Versioned: false,
})
```

**Encore.ts:**

See [Object storage](/docs/ts/primitives/object-storage).

**Encore.go:**

See [Object storage](/docs/go/primitives/object-storage).

### Scheduled tasks

```hcl
resource "aws_cloudwatch_event_rule" "welcome_email" {
  schedule_expression = "rate(2 hours)"
}
resource "aws_cloudwatch_event_target" "welcome_email" { ... }
resource "aws_lambda_permission" "welcome_email" { ... }
```

**Encore.ts:**

```ts
import { CronJob } from "encore.dev/cron";

const _ = new CronJob("welcome-email", {
  title: "Send welcome emails",
  every: "2h",
  endpoint: sendWelcomeEmail,
});
```

**Encore.go:**

```go
import "encore.dev/cron"

var _ = cron.NewJob("welcome-email", cron.JobConfig{
    Title:    "Send welcome emails",
    Every:    2 * cron.Hour,
    Endpoint: SendWelcomeEmail,
})
```

**Encore.ts:**

The target is a reference to the endpoint function rather than an ARN. See [Cron
jobs](/docs/ts/primitives/cron-jobs).

**Encore.go:**

The target is a reference to the endpoint function rather than an ARN. See [Cron
jobs](/docs/go/primitives/cron-jobs).

### Secrets

```hcl
resource "aws_secretsmanager_secret" "github_token" { name = "GitHubAPIToken" }
```

**Encore.ts:**

```ts
import { secret } from "encore.dev/config";

const githubToken = secret("GitHubAPIToken");
```

**Encore.go:**

```go
var secrets struct {
    GitHubAPIToken string
}
```

**Encore.ts:**

Values are set per environment with `encore secret set`. See
[Secrets](/docs/ts/primitives/secrets).

**Encore.go:**

Values are set per environment with `encore secret set`. See
[Secrets](/docs/go/primitives/secrets).

## Concept mapping

**Declaring infrastructure**

| Terraform | Encore |
|---|---|
| `resource` block | Resource constructor in application code |
| `provider` block | Cloud provider is chosen per environment in the dashboard |
| Resource reference (`aws_sns_topic.x.arn`) | Language-level import of the resource object |
| `depends_on` | Usually derived from resource usage and service relationships; no direct equivalent |
| Modules | No direct equivalent; services organize application code and resources |
| `count` and `for_each` | No equivalent for Encore resources; declarations must be statically discoverable |

**Configuration and state**

| Terraform | Encore |
|---|---|
| Input variables and `locals` | No single equivalent; infrastructure settings, secrets, and other application configuration are handled separately |
| `output` and `var` plumbing | Not required; the consumer imports the object |
| `tfvars` per environment | No single equivalent; Encore Cloud settings, secrets, and other application configuration are handled separately |
| Workspaces | Environments are the closest operational concept, but their lifecycle and configuration differ |
| State file | The application model is derived from source; Encore Cloud separately maintains infrastructure state |
| Remote state backend | No direct equivalent for platform-managed environments; Encore Cloud maintains its infrastructure records |

**Running it**

| Terraform | Encore |
|---|---|
| `terraform plan` | No direct equivalent; builds validate the application model and Encore Cloud provides infrastructure change workflows |
| `terraform apply` | No direct equivalent; an Encore Cloud deployment provisions supported changes, while self-hosted infrastructure remains in your IaC workflow |
| `terraform import` | No direct equivalent; use the resource-specific Encore import workflow to connect supported existing infrastructure |
| `data` source for an Encore resource | [Encore Terraform Provider](/docs/platform/integrations/terraform) data sources |
| External migration step | `migrations/` directory, applied on deploy |
| Hand-written IAM policies | Derived from supported resource usage in the application model |

## Using Encore with Terraform

Encore and Terraform can be used together. Encore can manage supported
application resources while Terraform manages other infrastructure, such as
DNS, organization-level networking, and third-party providers.

### Pointing Encore at resources you already have

Encore provides resource-specific workflows for connecting existing [RDS](/docs/platform/infrastructure/aws/import-rds), [Cloud SQL](/docs/platform/infrastructure/gcp/import-cloud-sql), [S3](/docs/platform/infrastructure/aws/import-s3-bucket), [GCS](/docs/platform/infrastructure/gcp/import-gcs-bucket), [SNS](/docs/platform/infrastructure/aws/import-sns-topic), and [Pub/Sub](/docs/platform/infrastructure/gcp/import-pubsub-topic) resources. At the environment level, it can also deploy into an existing [GKE cluster](/docs/platform/infrastructure/import-kubernetes-cluster) or [GCP project](/docs/platform/infrastructure/gcp/import-project).

These workflows do not read or convert Terraform state. Follow the guide for the resource before changing its Terraform ownership.

If ownership moves to Encore, remove the resource from Terraform without
destroying it. Terraform recommends replacing its `resource` block with a
[`removed` block](https://developer.hashicorp.com/terraform/language/state/remove),
then reviewing and applying the plan. `destroy = false` goes inside a nested
`lifecycle` block:

```hcl
removed {
  from = aws_s3_bucket.uploads

  lifecycle {
    destroy = false
  }
}
```

### Reading Encore resources from Terraform

The [Encore Terraform Provider](/docs/platform/integrations/terraform) exposes
data sources such as `encore_database`, `encore_cache`, and
`encore_pubsub_topic`, so Terraform can reference infrastructure Encore created:

```hcl
data "encore_pubsub_topic" "topic" {
  name = "my-topic"
  env  = "my-env"
}

resource "aws_iot_topic_rule" "rule" {
  name = "my-rule"
  sql  = "SELECT * FROM 'my-topic'"
  sns {
    message_format = "RAW"
    role_arn       = aws_iam_role.role.arn
    target_arn     = data.encore_pubsub_topic.topic.aws_sns.arn
  }
}
```

### Self-hosting and binding manually

Build a Docker image with `encore build docker` and pass an
`infra.config.json` that maps each resource in the derived model to a physical
resource you provision separately:

```bash
encore build docker myapp:latest --config ./infra.config.json
```

Running the build without `--config` prints the resources the file has to
account for. See [Configure
infrastructure](/docs/self-host/configure-infra).

## Workflow differences

Encore validates statically discoverable resource declarations during the
build and configures their deployment per environment. There is no direct
equivalent to `terraform plan`, `count`, or `for_each`. Keep unsupported,
shared, and dynamically generated infrastructure in Terraform and migrate one
ownership boundary at a time.

## Why configuration is split this way

Application-level properties stay with the resource declaration. For a SQL
database, these include its logical name and migration directory. Deployment
properties such as the database engine and capacity are configured per
environment.

Resource declarations can be used locally and in AWS or GCP environments. New
environments select their own infrastructure settings, including [preview
environments](/docs/platform/deploy/preview-environments).
Capacity and other deployment settings can be managed through [infrastructure
configuration](/docs/platform/infrastructure/configuration) without changing
the resource declaration.

The static declarations also let Encore determine which resources exist and
which services use them. Encore uses that information to derive IAM policies
and validate supported resource usage. For agentic workflows, [AI
infrastructure provisioning](/docs/platform/ai-integration) describes the
available controls, and [infrastructure
namespaces](/docs/infra-namespaces) provide isolated local state.

See [Encore Application Model](/docs/application-model) for details
on how declarations are analyzed.
