Skip to content

Service

Source: src/AWS/AppRunner/Service.ts

An AWS App Runner service — the zero-infrastructure way to run a container behind an HTTPS endpoint: App Runner provisions, load-balances, scales, and patches the fleet for you. Service creation and deletion are asynchronous and take several minutes; the provider waits (bounded) for operations to settle.

Service is a Platform: alongside the low-level container-image form (imageRepository), it supports Effect-native implementations — an inline Effect HTTP program that Alchemy bundles, containerizes, pushes to a managed ECR repository, and deploys, provisioning the instance and ECR access roles automatically. Capability bindings (e.g. DynamoDB GetItem) attach IAM policy statements to the managed instance role.

Public ECR Image

const service = yield* AppRunner.Service("Hello", {
imageRepository: {
imageIdentifier: "public.ecr.aws/aws-containers/hello-app-runner:latest",
imageRepositoryType: "ECR_PUBLIC",
port: "8000",
},
instanceConfiguration: { cpu: "256", memory: "512" },
});
// service.serviceUrl -> "xxxxxxxx.us-west-2.awsapprunner.com"

Private ECR Image with Access Role

const service = yield* AppRunner.Service("Api", {
imageRepository: {
imageIdentifier: `${repository.repositoryUri}:latest`,
imageRepositoryType: "ECR",
port: "8080",
runtimeEnvironmentVariables: { NODE_ENV: "production" },
},
accessRoleArn: accessRole.roleArn,
autoDeploymentsEnabled: true,
});
export default class Api extends AppRunner.Service<Api>()(
"Api",
{
main: import.meta.url,
port: 3000,
instanceConfiguration: { cpu: "256", memory: "512" },
},
Effect.gen(function* () {
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
return HttpServerResponse.text("hello from app runner");
}),
};
}),
) {}

main is bundled with rolldown at deploy time. Top-level calls in the effect, @effect/*, alchemy, @alchemy.run/*, and @distilled.cloud/* packages receive #__PURE__ annotations by default, so anything the service doesn’t use from those packages is tree-shaken out of the bundle. Any other package — including your own app — is left untouched unless you list it explicitly.

Treat additional packages as pure

Pass package names (or picomatch globs) via build.pure.packages to annotate them in addition to the defaults.

{
main: import.meta.url,
build: {
pure: { packages: ["my-lib", "@my-scope/*"] },
},
}

Listing a package annotates calls whose result is bound (variable initializers, exports) — safe anywhere. If a listed package also declares "sideEffects": false (or []) in its package.json, that combination opts it into full annotation: top-level calls whose result is discarded (e.g. router.on("/path", handler) registrations) are also marked pure and deleted under minification when unused. Only list a sideEffects: false package if its modules really are free of meaningful top-level side effects. The effect, alchemy, and @distilled.cloud defaults declare exactly that, on purpose — their modules are designed to be fully tree-shakeable.

Disable pure annotations

{
main: import.meta.url,
build: { pure: false },
}
const service = yield* AppRunner.Service("Api", {
imageRepository: {
imageIdentifier: "public.ecr.aws/aws-containers/hello-app-runner:latest",
imageRepositoryType: "ECR_PUBLIC",
port: "8000",
},
autoScalingConfigurationArn: scaling.autoScalingConfigurationArn,
networkConfiguration: {
egressType: "VPC",
vpcConnectorArn: connector.vpcConnectorArn,
},
});