1import * as Alchemy from "alchemy";
2import * as Docker from "alchemy/Docker";
3import * as Config from "effect/Config";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
6import * as Option from "effect/Option";
7
8const POSTGRES_PORT = 15432;
9const POSTGRES_CONTAINER = "alchemy-example-postgres";
10
11export default Alchemy.Stack(
12 "DockerPostgresExample",
13 {
14 providers: Layer.merge(Docker.providers(), Alchemy.RandomProvider()),
15 state: Alchemy.localState(),
16 },
17 Effect.gen(function* () {
18 const configuredPassword = yield* Config.Redacted("POSTGRES_PASSWORD").pipe(
19 Config.option,
20 );
21 const password = yield* Option.match(configuredPassword, {
22 onSome: Effect.succeed,
23 onNone: () => Alchemy.makeRandom("PostgresPassword", { bytes: 16 }),
24 });
25
26 const image = yield* Docker.RemoteImage("postgres-image", {
27 name: "postgres",
28 tag: "18-alpine",
29 alwaysPull: false,
30 });
31 const network = yield* Docker.Network("app-network");
32 const data = yield* Docker.Volume("postgres-data");
33
34 const postgres = yield* Docker.Container("postgres", {
35 name: POSTGRES_CONTAINER,
36 image,
37 environment: {
38 POSTGRES_DB: "app",
39 POSTGRES_USER: "alchemy",
40 POSTGRES_PASSWORD: password,
41 },
42 ports: [{ external: POSTGRES_PORT, internal: 5432 }],
43 volumes: [
44 {
45 hostPath: data.name,
46 containerPath: "/var/lib/postgresql/data",
47 },
48 ],
49 networks: [{ name: network.name, aliases: ["postgres"] }],
50 healthcheck: {
51 cmd: ["CMD-SHELL", "pg_isready -U alchemy -d app"],
52 interval: "5 seconds",
53 timeout: "5 seconds",
54 retries: 10,
55 },
56 start: true,
57 });
58 return {
59 container: postgres.name,
60 image: image.imageRef,
61 network: network.name,
62 volume: data.name,
63 hostPort: postgres.ports,
64 };
65 }),
66);