forge

package module
v0.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 28, 2026 License: MIT Imports: 20 Imported by: 0

README

forge — Go SST

A drop-in replacement for SST written in Go. Powered by Pulumi under the hood — state files are fully compatible with SST v3 Ion.

SST entered maintenance mode in 2025 after the team shifted focus to OpenCode. forge picks up where SST left off, with a native Go config and zero Node.js dependency.

Features

Feature SST v3 forge
Config language TypeScript Go
IaC engine Pulumi (Ion) Pulumi (compatible state)
deploy / remove / diff
Live Lambda dev tunnel ✓ (SQS relay)
Secrets (SSM)
Multi-stage config
Per-stage AWS profile/region
Protected stages
Project scaffolding forge create
Migration tool forge migrate
Cloudflare Workers / KV / D1 / R2
Static site (S3 + CloudFront)
Next.js (SSR + static)
Node.js required Only for NewNextjsSite

Installation

curl -fsSL https://raw.githubusercontent.com/nimbus-local/forge/master/install.sh | sh

Or install from source:

go install github.com/nimbus-local/forge/cmd/forge@latest

Pulumi is downloaded automatically on first deploy to ~/.forge/pulumi/ — no separate install needed.


Quick start

# Scaffold a new project
forge create my-api --template go-api
cd my-api

# Deploy to AWS
forge deploy

Available templates: go-api, go-crud, go-worker, fullstack


Migrating from SST

1. One-command migration
# Inside your existing SST project:
forge migrate                        # converts ./sst.config.ts → ./infra/sst.config.go
forge migrate path/to/sst.config.ts  # explicit path
2. Review the output

The migrator handles the most common patterns automatically and emits // TODO: comments for anything that needs manual attention.

cat infra/sst.config.go   # review
cd infra && go mod tidy   # fetch dependencies
3. Deploy
forge diff --stage dev      # preview changes
forge deploy --stage dev
forge deploy --stage production
State file compatibility

If you were already on SST v3 Ion, your Pulumi state is in S3 and is fully compatible. Set FORGE_STATE_BUCKET to point at the same bucket:

export FORGE_STATE_BUCKET=my-app-dev-forge-state-123456789012
forge diff   # should show zero changes if the config was migrated correctly

Examples

Example Description
examples/checklist-simple Next.js + DynamoDB, anonymous cookie-keyed lists
examples/checklist-full Next.js + Go Lambda + DynamoDB + GitHub OAuth
examples/smoke Smoke-test app — exercises every construct (Function, Queue, Topic, Cron, Secret, DynamoDB, Bucket, ApiGatewayV2)
examples/sst.config.go Reference todo API (DynamoDB + Lambda + S3 + API Gateway)

Writing a config from scratch

Create infra/sst.config.go:

package main

import (
    forge "github.com/nimbus-local/forge"
    "github.com/nimbus-local/forge/constructs"
)

func main() {
    forge.Run(&forge.Config{
        App: &forge.AppConfig{
            Name: "my-app",
            Home: "aws",
        },
        // Optional: per-stage overrides
        Stages: map[string]*forge.StageConfig{
            "production": {
                Protected:  true,
                AWSProfile: "prod",
            },
        },
        Run: func(ctx *forge.RunContext) error {
            table := constructs.NewDynamoDB(ctx, "UsersTable", &constructs.DynamoDBArgs{
                PrimaryIndex: constructs.PrimaryIndex{PartitionKey: "id"},
            })

            fn := constructs.NewFunction(ctx, "Api", &constructs.FunctionArgs{
                Handler: "bootstrap",
                Link:    []forge.Linkable{table},
            })

            api := constructs.NewApiGatewayV2(ctx, "Api", nil)
            api.Route("GET /users", &constructs.RouteArgs{Function: fn})

            ctx.Export("url", api.URL())
            return nil
        },
    })
}

Commands

forge create <name> [-t <template>]   Scaffold a new project
forge deploy [--stage <stage>]        Deploy your stack
forge dev    [--stage <stage>]        Live Lambda dev tunnel
forge diff   [--stage <stage>]        Preview changes (no deploy)
forge remove [--stage <stage>]        Destroy a stage
forge stages                          List deployed stages
forge secret set   <NAME> <VAL>       Store a secret in SSM
forge secret get   <NAME>             Retrieve a secret
forge secret remove <NAME>            Delete a secret
forge secret list                     List all secrets for stage
forge migrate [sst.config.ts]         Convert TS config to Go
forge bootstrap [--stage <stage>]     Create the Pulumi state S3 bucket
forge console   [--stage <stage>]     Open the web console (outputs, resources, secrets)
Global flags
--stage, -s   Deployment stage (default: $USER or "dev")
--profile     AWS credentials profile
--region      AWS region
--config      Path to sst.config.go (default: ./infra/sst.config.go)

Constructs

AWS

All constructs implement forge.Linkable — link them to a Function to inject their identifiers as environment variables.

Construct Usage Env vars injected
NewFunction Lambda function SST_FUNCTION_<NAME>_ARN
NewApiGatewayV2 HTTP API + routes SST_API_<NAME>_URL
NewDynamoDB DynamoDB table SST_TABLE_<NAME>_NAME, SST_TABLE_<NAME>_ARN
NewBucket S3 bucket SST_BUCKET_<NAME>_NAME, SST_BUCKET_<NAME>_ARN
NewCron EventBridge schedule → Lambda
NewQueue SQS queue + optional consumer SST_QUEUE_<NAME>_URL, SST_QUEUE_<NAME>_ARN
NewTopic SNS topic + subscribers SST_TOPIC_<NAME>_ARN
NewSecret SSM SecureString at deploy time SST_SECRET_<NAME>
NewKMSKey Customer-managed KMS key SST_KMS_<NAME>_ARN, SST_KMS_<NAME>_ID
NewStaticSite S3 + CloudFront static website SST_SITE_<NAME>_URL
NewNextjsSite Next.js on S3 + CloudFront + Lambda SST_SITE_<NAME>_URL
NewService ECS Fargate service + optional ALB SST_SERVICE_<NAME>_URL
Cloudflare

Set App.Home to "cloudflare" or "aws+cloudflare" and provide credentials via CLOUDFLARE_API_TOKEN (or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL).

App: &forge.AppConfig{
    Name: "my-app",
    Home: "aws+cloudflare",
    Cloudflare: &forge.CloudflareConfig{
        AccountID: "abc123", // or set CLOUDFLARE_ACCOUNT_ID
    },
},
Construct Package Env vars injected
NewWorker constructs/cloudflare SST_WORKER_<NAME>_NAME
NewKVNamespace constructs/cloudflare SST_KV_<NAME>_ID, SST_KV_<NAME>_NAME
NewD1Database constructs/cloudflare SST_D1_<NAME>_ID, SST_D1_<NAME>_NAME
NewR2Bucket constructs/cloudflare SST_R2_<NAME>_NAME

Worker bindings (KV, D1, R2 accessible as JS globals in the Worker):

kv := cf.NewKVNamespace(ctx, "Cache", nil)

cf.NewWorker(ctx, "Api", &cf.WorkerArgs{
    Handler:    "../worker/index.js",
    KVBindings: []*cf.KVNamespace{kv},
})

Multi-stage config

forge.Run(&forge.Config{
    App: &forge.AppConfig{Name: "my-app", Home: "aws"},
    Stages: map[string]*forge.StageConfig{
        "production": {
            Protected:  true,      // forge remove requires --force
            AWSProfile: "prod",
            AWSRegion:  "us-east-1",
            Tags:       map[string]string{"env": "production"},
        },
    },
    Run: func(ctx *forge.RunContext) error {
        if ctx.IsProduction() {
            // production-only resources
        }
        return nil
    },
})

Project structure

my-app/
├── infra/
│   ├── go.mod           ← infra module (imports forge)
│   └── sst.config.go    ← infrastructure definition
├── functions/
│   ├── api/
│   │   └── main.go      ← Lambda handler (compiled separately)
│   └── worker/
│       └── main.go
└── go.mod               ← app module (no forge/Pulumi dependency)

The infra/ directory is a separate Go module so Lambda handler binaries don't carry Pulumi as a dependency.


Environment variables

Variable Default Description
FORGE_STATE_BUCKET <app>-<stage>-forge-state-<accountId> S3 bucket for Pulumi state
FORGE_STAGE $USER or dev Active stage
PULUMI_CONFIG_PASSPHRASE "" State encryption passphrase
AWS_PROFILE AWS credentials profile
AWS_DEFAULT_REGION AWS region
CLOUDFLARE_API_TOKEN Cloudflare auth (preferred)
CLOUDFLARE_API_KEY Cloudflare auth (with EMAIL)
CLOUDFLARE_ACCOUNT_ID Cloudflare account ID
CLOUDFLARE_ZONE_ID Cloudflare zone (for Worker domains)

Roadmap

Phase 1 — Foundation (complete)
  • Bootstrap command + S3 state bucket auto-creation
  • Multi-stage config with per-stage AWS profile, region, tags, and protected stages
  • AWS constructs — Cron, Queue, Topic, Secret
  • Cloudflare support — Worker, KV, D1, R2
  • Project templates — forge create with go-api, go-crud, go-worker, fullstack
  • Full godoc + docs site
  • Deploy / destroy / diff summary tables
  • Dev tunnel stub binary (forge-stub)
  • Static site (NewStaticSite) and Next.js site (NewNextjsSite) with CloudFront host-header forwarding and image optimisation Lambda
  • Fargate / ECS construct (NewService)
  • GitHub Actions CI/CD integration guide
  • Web console (forge console)
Phase 2 — Testing & Nimbus validation (current)

The goal of this phase is to confirm forge is a reliable SST replacement before declaring it production-ready. Testing runs in two tiers: real AWS first to establish ground truth, then Nimbus (a LocalStack-compatible AWS emulator) to give every contributor a fast, account-free CI target.

Test suite

  • Unit tests — migrate/, secrets/, constructs/helpers, internal/bootstrap (70%+ coverage)
  • Integration test helpers — MustDeploy / MustRemove wrappers for real AWS stacks
  • E2E CLI tests — forge deploy, forge diff, forge migrate, forge secret against the compiled binary

AWS validation

  • Deploy checklist-simple to AWS and verify end-to-end (Next.js → API Gateway → Lambda → DynamoDB)
  • Deploy checklist-full to AWS and verify end-to-end (Next.js + GitHub OAuth + Go Lambda + DynamoDB)
  • Dedicated smoke-test app exercising every construct: Function, Queue, Topic, Cron, Service, Secret

Nimbus parity

forge already supports FORGE_AWS_ENDPOINT for redirecting Pulumi and all AWS SDK calls to a local emulator. This milestone adds structured assertions on top of that.

  • Deploy examples/smoke against Nimbus — assert every construct creates resources, link env vars are injected, and the API Gateway route responds correctly
  • Verify forge dev tunnel over Nimbus SQS (request/response queues round-trip locally)
  • CI gate: Nimbus deployment job in GitHub Actions that runs the smoke app on every PR (no AWS account or credentials required)
Phase 3 — Hardening
  • KMS encryption + configurable log retention and S3 lifecycle — NewKMSKey, KMSKeyArn on all constructs, LogRetentionDays, LifecycleDays
  • Aurora / RDS construct (NewDatabase) — RDS Postgres/MySQL + connection string injection
  • ElastiCache construct (NewCache) — Redis/Valkey cluster + connection string injection
  • Drift detection — forge drift compares live AWS state against Pulumi state

License

MIT

Documentation

Overview

Package forge is the Go replacement for SST (Serverless Stack). Import this in your infra/sst.config.go and call forge.Run() from main(). The forge CLI sets FORGE_MODE to control deploy/dev/remove behaviour.

Index

Constants

View Source
const PulumiVersion = "3.247.0"

PulumiVersion is the Pulumi CLI version bundled and managed by forge. Update this alongside the pulumi/sdk/v3 dependency in go.mod.

Variables

This section is empty.

Functions

func Run

func Run(cfg *Config)

Run is the single entry point for your sst.config.go. It reads FORGE_MODE and FORGE_STAGE set by the CLI and acts accordingly.

func main() { forge.Run(&forge.Config{ ... }) }

Types

type AppConfig

type AppConfig struct {
	Name       string
	Home       string        // "aws" | "cloudflare" | "aws+cloudflare"
	Removal    RemovalPolicy // default: RemovalDestroy
	Cloudflare *CloudflareConfig
}

AppConfig holds project-level metadata.

type CloudflareConfig

type CloudflareConfig struct {
	// AccountID is the Cloudflare account ID. Defaults to CLOUDFLARE_ACCOUNT_ID.
	AccountID string
	// ZoneID is the Cloudflare zone ID used for custom Worker domains. Defaults to CLOUDFLARE_ZONE_ID.
	ZoneID string
}

CloudflareConfig holds Cloudflare account settings used by CF constructs. Fields default to the corresponding CLOUDFLARE_* environment variables.

type Config

type Config struct {
	App    *AppConfig
	Stages map[string]*StageConfig // per-stage overrides; key is the stage name
	Run    func(ctx *RunContext) error
}

Config is the top-level definition of your infrastructure. Create one in infra/sst.config.go and pass it to forge.Run().

type DevHandler added in v0.3.3

type DevHandler struct {
	ARN        string `json:"arn"`
	HandlerSrc string `json:"handlerSrc"`
}

DevHandler holds the resolved ARN and local source path for one function.

type DevOutputFile added in v0.3.3

type DevOutputFile struct {
	RequestQueueURL  string                `json:"requestQueueUrl"`
	ResponseQueueURL string                `json:"responseQueueUrl"`
	Handlers         map[string]DevHandler `json:"handlers"`
}

DevOutputFile is the JSON structure written to FORGE_DEV_OUTPUT_FILE after a successful dev-mode deploy. The CLI reads it to start the local tunnel.

type Linkable

type Linkable interface {
	LinkEnv() pulumi.StringMap
	LinkName() string
}

Linkable is implemented by any construct that can be linked to a Function (injecting its ARNs / URLs as environment variables at deploy time). Only constructs provided by this module are intended to implement this interface.

type RemovalPolicy

type RemovalPolicy string

RemovalPolicy controls what happens to resources when a stage is torn down.

const (
	RemovalDestroy            RemovalPolicy = "destroy"
	RemovalRetain             RemovalPolicy = "retain"
	RemovalRetainOnProtection RemovalPolicy = "retain-on-protection"
)

type RunContext

type RunContext struct {
	Stage       string
	App         *AppConfig
	AccountID   string // AWS account ID — used to ensure globally unique resource names
	WorkDir     string // absolute path to the infra/ directory at deploy time
	DevMode     bool
	IsProtected bool
	// contains filtered or unexported fields
}

RunContext is passed to your Config.Run function. Use it to create constructs and export stack outputs.

func NewRunContext added in v0.3.2

func NewRunContext(pctx *pulumi.Context, app *AppConfig, stage, accountID string) *RunContext

NewRunContext constructs a RunContext suitable for testing infrastructure programs. Pass the *pulumi.Context received inside a pulumi.RunErr callback that uses pulumi.WithMocks.

err := pulumi.RunErr(func(pctx *pulumi.Context) error {
    ctx := forge.NewRunContext(pctx, &forge.AppConfig{Name: "myapp"}, "test", "123456789012")
    // create constructs and assert on them
    return nil
}, pulumi.WithMocks("myapp", "test", mocks))

func (*RunContext) DevQueues added in v0.3.3

func (r *RunContext) DevQueues() (reqURL, resURL pulumi.StringOutput, ok bool)

DevQueues returns the shared SQS queue URLs set by the first dev-mode NewFunction.

func (*RunContext) Export

func (r *RunContext) Export(name string, value interface{})

Export exposes a stack output visible in `forge deploy` output and the SST Console. value must be a pulumi.Output or a plain string/int.

func (*RunContext) ExtraTags

func (r *RunContext) ExtraTags() map[string]string

ExtraTags returns the additional resource tags configured for this stage via StageConfig.Tags.

func (*RunContext) IsProduction

func (r *RunContext) IsProduction() bool

IsProduction returns true when the active stage is "production" or "prod".

func (*RunContext) Pulumi

func (r *RunContext) Pulumi() *pulumi.Context

Pulumi returns the underlying pulumi.Context for advanced use cases.

func (*RunContext) SetDevQueues added in v0.3.3

func (r *RunContext) SetDevQueues(reqURL, resURL pulumi.StringOutput)

SetDevQueues stores the shared SQS queue URLs for the dev tunnel and exports them as stack outputs. Called by constructs.NewFunction on the first dev-mode function. Subsequent calls are no-ops.

func (*RunContext) StageIn

func (r *RunContext) StageIn(stages ...string) bool

StageIn returns true if the active stage matches any of the provided names.

type StageConfig

type StageConfig struct {
	// Removal overrides the base AppConfig removal policy for this stage.
	Removal RemovalPolicy
	// AWSProfile uses a different AWS credentials profile when deploying this stage.
	AWSProfile string
	// AWSRegion deploys this stage to a different AWS region.
	AWSRegion string
	// Protected means `forge remove` requires --force to proceed.
	Protected bool
	// Tags adds extra resource tags for every resource in this stage.
	Tags map[string]string
}

StageConfig holds per-stage overrides applied on top of the base AppConfig.

Directories

Path Synopsis
cmd
forge command
Package main is the forge CLI — a drop-in replacement for the sst CLI.
Package main is the forge CLI — a drop-in replacement for the sst CLI.
forge-stub command
forge-stub is the thin proxy Lambda binary deployed by `forge dev`.
forge-stub is the thin proxy Lambda binary deployed by `forge dev`.
cloudflare
Package cloudflare provides Pulumi constructs for Cloudflare resources (Workers, KV, D1, R2).
Package cloudflare provides Pulumi constructs for Cloudflare resources (Workers, KV, D1, R2).
Package dev implements the live Lambda development tunnel.
Package dev implements the live Lambda development tunnel.
Example infra/sst.config.go This is what a typical forge project looks like.
Example infra/sst.config.go This is what a typical forge project looks like.
internal
bootstrap
Package bootstrap creates and validates the S3 bucket used for Pulumi state storage.
Package bootstrap creates and validates the S3 bucket used for Pulumi state storage.
pulumibundle
Package pulumibundle ensures the Pulumi CLI binary is available, downloading it automatically when it is not found on PATH.
Package pulumibundle ensures the Pulumi CLI binary is available, downloading it automatically when it is not found on PATH.
templates
Package templates embeds the forge project template files.
Package templates embeds the forge project template files.
Package migrate converts an existing sst.config.ts to sst.config.go.
Package migrate converts an existing sst.config.ts to sst.config.go.
Package secrets provides SSM Parameter Store backed secrets management, equivalent to SST's `sst secret` commands.
Package secrets provides SSM Parameter Store backed secrets management, equivalent to SST's `sst secret` commands.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL