Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c3d9406
feat(docker): add Docker resources
austinm911 Jun 18, 2026
7470843
fix(docker): refine inspect API and example secret
austinm911 Jun 19, 2026
6b93eb5
Merge remote-tracking branch 'origin/main' into codex/docker-v2-resou…
sam-goodwin Jun 25, 2026
41fce13
fix(docker): align with main after merge
sam-goodwin Jun 25, 2026
60c1eb2
refactor(docker): auto physical names + drop Date.now() from images
sam-goodwin Jun 25, 2026
0e948ae
move helpers to bottom
john-royal Jun 25, 2026
3b2f7bd
refactor
john-royal Jun 25, 2026
4cee735
update aws and cloudflare to use new docker api
john-royal Jun 25, 2026
45b7a4b
replace old docker utils with Docker.materialize
john-royal Jun 25, 2026
e10aba6
remove leftover helpers
john-royal Jun 25, 2026
a7c3125
progress
john-royal Jun 25, 2026
9ff3c6f
add comments to docker client
john-royal Jun 25, 2026
57fc593
fix any in providers
john-royal Jun 25, 2026
ef3b931
allow RemoteImage to re-tag and push; remove remote pull from Image
john-royal Jun 25, 2026
61b2040
write docker build logs as session notes
john-royal Jun 25, 2026
6a8373c
add more tests
john-royal Jun 26, 2026
c2fe7fd
more tests
john-royal Jun 26, 2026
3092a58
replace fix (separate pr)
john-royal Jun 26, 2026
f34cfae
log when pushing image
john-royal Jun 26, 2026
a04dc6e
Merge branch 'main' into codex/docker-v2-resources
john-royal Jun 26, 2026
745bca5
add back inspectContainer
john-royal Jun 26, 2026
d9f7000
fix type errors
john-royal Jun 26, 2026
72699e2
simplify image build
john-royal Jun 26, 2026
1d3e2d6
mv helper
john-royal Jun 26, 2026
ce18557
relocate helpers
john-royal Jun 26, 2026
29acf6a
fix import
john-royal Jun 26, 2026
ba6d340
claude pr feedback fixes
john-royal Jun 26, 2026
d67cdfb
Merge branch 'main' into codex/docker-v2-resources
john-royal Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions examples/docker-postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Docker PostgreSQL Example

This example provisions PostgreSQL 18 Alpine with Docker resources:

- `Docker.RemoteImage` pulls `postgres:18-alpine`
- `Docker.Network` creates an app network
- `Docker.Volume` creates persistent database storage
- `Docker.Container` starts PostgreSQL with a redacted password, a network alias, a named volume, and a host port
- `Docker.inspectContainer` returns the bound host port

Docker resources use the active Docker CLI context. That can be Docker Desktop, a remote Docker host, an SSH context, or a CI runner daemon.

These resources are separate from `Cloudflare.Container`. The bridge between Docker and cloud container platforms is an image reference or registry digest, not a swappable container object.

## Commands

```sh
bun install
POSTGRES_PASSWORD=change-me bun run --filter docker-postgres-example deploy
bun run --filter docker-postgres-example destroy
```

If `POSTGRES_PASSWORD` is omitted, the example generates and stores a redacted password with `Alchemy.makeRandom`.

The stack publishes PostgreSQL on `localhost:15432`:

```sh
psql postgres://alchemy:change-me@localhost:15432/app
```
66 changes: 66 additions & 0 deletions examples/docker-postgres/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as Alchemy from "alchemy";
import * as Docker from "alchemy/Docker";
import * as Config from "effect/Config";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Option from "effect/Option";

const POSTGRES_PORT = 15432;
const POSTGRES_CONTAINER = "alchemy-example-postgres";

export default Alchemy.Stack(
"DockerPostgresExample",
{
providers: Layer.merge(Docker.providers(), Alchemy.RandomProvider()),
state: Alchemy.localState(),
},
Effect.gen(function* () {
const configuredPassword = yield* Config.redacted("POSTGRES_PASSWORD").pipe(
Config.option,
);
const password = yield* Option.match(configuredPassword, {
onSome: Effect.succeed,
onNone: () => Alchemy.makeRandom("PostgresPassword", { bytes: 16 }),
});

const image = yield* Docker.RemoteImage("postgres-image", {
name: "postgres",
tag: "18-alpine",
alwaysPull: false,
});
const network = yield* Docker.Network("app-network");
const data = yield* Docker.Volume("postgres-data");

const postgres = yield* Docker.Container("postgres", {
name: POSTGRES_CONTAINER,
image,
environment: {
POSTGRES_DB: "app",
POSTGRES_USER: "alchemy",
POSTGRES_PASSWORD: password,
},
ports: [{ external: POSTGRES_PORT, internal: 5432 }],
volumes: [
{
hostPath: data.name,
containerPath: "/var/lib/postgresql/data",
},
],
networks: [{ name: network.name, aliases: ["postgres"] }],
healthcheck: {
cmd: ["CMD-SHELL", "pg_isready -U alchemy -d app"],
interval: "5 seconds",
timeout: "5 seconds",
retries: 10,
},
start: true,
});
return {
container: postgres.name,
image: image.imageRef,
network: network.name,
volume: data.name,
hostPort: postgres.ports,
};
}),
);
20 changes: 20 additions & 0 deletions examples/docker-postgres/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "docker-postgres-example",
"version": "0.0.0",
"private": true,
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/alchemy-run/alchemy-effect.git",
"directory": "examples/docker-postgres"
},
"type": "module",
"scripts": {
"deploy": "alchemy deploy",
"destroy": "alchemy destroy"
},
"dependencies": {
"alchemy": "workspace:*",
"effect": "catalog:"
}
}
16 changes: 16 additions & 0 deletions examples/docker-postgres/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "../../tsconfig.base.json",
"include": ["alchemy.run.ts"],
"compilerOptions": {
"noEmit": true,
"rootDir": ".",
"module": "Preserve",
"moduleResolution": "Bundler",
"target": "ESNext"
},
"references": [
{
"path": "../../packages/alchemy/tsconfig.json"
}
]
}
6 changes: 6 additions & 0 deletions packages/alchemy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@
"worker": "./src/Bundle/index.ts",
"import": "./lib/Bundle/index.js"
},
"./Docker": {
"types": "./lib/Docker/index.d.ts",
"bun": "./src/Docker/index.ts",
"worker": "./src/Docker/index.ts",
"import": "./lib/Docker/index.js"
},
"./ContentType": {
"types": "./lib/ContentType.d.ts",
"bun": "./src/ContentType.ts",
Expand Down
21 changes: 9 additions & 12 deletions packages/alchemy/src/AWS/ECS/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@ import * as Stream from "effect/Stream";
import type * as rolldown from "rolldown";
import { AlchemyContext } from "../../AlchemyContext.ts";
import * as Bundle from "../../Bundle/Bundle.ts";
import {
dockerBuild,
materializeDockerfile,
pushImage,
writeContextFiles,
} from "../../Bundle/Docker.ts";
import {
findCwdForBundle,
getStableContextDir,
} from "../../Bundle/TempRoot.ts";
import { isResolved } from "../../Diff.ts";
import { Docker } from "../../Docker/Docker.ts";
import * as Output from "../../Output.ts";
import { createPhysicalName } from "../../PhysicalName.ts";
import { Platform, type Main, type PlatformProps } from "../../Platform.ts";
Expand Down Expand Up @@ -345,6 +340,7 @@ export const TaskProvider = () =>
Task,
Effect.gen(function* () {
const stack = yield* Stack;
const docker = yield* Docker;

const { dotAlchemy } = yield* AlchemyContext;
const fs = yield* FileSystem.FileSystem;
Expand Down Expand Up @@ -724,15 +720,16 @@ await Effect.runPromise(program);
);
const registry = credentials.proxyEndpoint.replace(/^https?:\/\//, "");

yield* materializeDockerfile(dockerfile, contextDir);
yield* writeContextFiles(contextDir, [
{ path: "index.mjs", content: code },
]);
yield* dockerBuild({
yield* docker.materialize({
context: contextDir,
dockerfile: dockerfile,
files: [{ path: "index.mjs", content: code }],
});
yield* docker.image.build({
tag: imageUri,
context: contextDir,
});
yield* pushImage(imageUri, {
yield* docker.image.push(imageUri, {
username: "AWS",
password,
server: registry,
Expand Down
2 changes: 2 additions & 0 deletions packages/alchemy/src/AWS/Providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import * as Schedule from "effect/Schedule";
import * as HttpClientError from "effect/unstable/http/HttpClientError";
import { CredentialsStoreLive } from "../Auth/Credentials.ts";
import * as Command from "../Command/index.ts";
import { DockerLive } from "../Docker/Docker.ts";
import { KeyPair, KeyPairProvider } from "../KeyPair.ts";
import * as Provider from "../Provider.ts";
import { Random, RandomProvider } from "../Random.ts";
Expand Down Expand Up @@ -342,6 +343,7 @@ export const providers = () =>
KeyPairProvider(),
RandomProvider(),
Assets.AssetsLive,
DockerLive,
),
),
Layer.provideMerge(Region.fromEnvironment),
Expand Down
Loading