Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
```
71 changes: 71 additions & 0 deletions examples/docker-postgres/alchemy.run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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 Option from "effect/Option";

const POSTGRES_PORT = 15432;
const POSTGRES_CONTAINER = "alchemy-example-postgres";
const EMPTY_RUNTIME: Docker.ContainerRuntimeInfo = { ports: {} };

export default Alchemy.Stack(
"DockerPostgresExample",
{ providers: Docker.providers(), 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: "5s",
timeout: "5s",
retries: 10,
},
start: true,
});

const runtime = yield* Docker.inspectContainer(POSTGRES_CONTAINER).pipe(
Effect.catchTag("DockerCommandError", () =>
Effect.succeed(EMPTY_RUNTIME),
),
);

return {
container: postgres.name,
image: image.imageRef,
network: network.name,
volume: data.name,
hostPort: runtime.ports["5432/tcp"] ?? POSTGRES_PORT,
connectionString: `postgres://alchemy:***@localhost:${POSTGRES_PORT}/app`,
};
}),
);
21 changes: 21 additions & 0 deletions examples/docker-postgres/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"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",
"build": "tsgo --noEmit -p tsconfig.json"
},
"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 @@ -107,6 +107,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
Loading