@alxia/create 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -9
- package/docs/guide.md +69 -20
- package/docs/roadmap.md +18 -4
- package/docs/troubleshooting.md +103 -2
- package/package.json +2 -2
- package/templates/api/Dockerfile +18 -14
- package/templates/api/README.md +25 -9
- package/templates/api/package.json +2 -2
- package/templates/api/src/server.ts +14 -0
- package/templates/react-router/Dockerfile +13 -14
- package/templates/react-router/README.md +1 -1
package/README.md
CHANGED
|
@@ -28,8 +28,8 @@ bun create @alxia my-site --template react-router
|
|
|
28
28
|
|
|
29
29
|
| template | what it writes |
|
|
30
30
|
| --- | --- |
|
|
31
|
-
| `api` | an `@alxia/core` app with Zod: `POST /todos` validates its body, behind `requireKey`, a hook made with `defineHook` that answers 401 without an API key; a `bun test` spec calling it with `app.request()` and through `@alxia/client`, typed; `bun dev` restarting on change, `typecheck`, `build`, a strict `tsconfig.json`, a `Dockerfile` on `oven/bun:1`, `.dockerignore`, `.gitignore`, `.env.example` and a README |
|
|
32
|
-
| `react-router` | React Router's official template, as `create-react-router` writes it, shipped in this package and copied, with [`@alxia/react-router`](https://www.npmjs.com/package/@alxia/react-router) added as its README says: `alxia()` in `vite.config.ts`'s plugins, `start` running `bun build/server/index.js`, a `bunfig.toml` starting React Router's CLI on Bun, and a `Dockerfile` on `oven/bun:1` in place of React Router's Node one. No server file: the default one serves the pages; `bunx alxia-react-router reveal` writes it out to customise |
|
|
31
|
+
| `api` | an `@alxia/core` app with Zod: `POST /todos` validates its body, behind `requireKey`, a hook made with `defineHook` that answers 401 without an API key; a `bun test` spec calling it with `app.request()` and through `@alxia/client`, typed; `bun dev` restarting on change, `typecheck`, `build`, a strict `tsconfig.json`, a `Dockerfile` running on `oven/bun:1-alpine`, `.dockerignore`, `.gitignore`, `.env.example` and a README |
|
|
32
|
+
| `react-router` | React Router's official template, as `create-react-router` writes it, shipped in this package and copied, with [`@alxia/react-router`](https://www.npmjs.com/package/@alxia/react-router) added as its README says: `alxia()` in `vite.config.ts`'s plugins, `start` running `bun build/server/index.js`, a `bunfig.toml` starting React Router's CLI on Bun, and a `Dockerfile` running on `oven/bun:1-alpine` in place of React Router's Node one. No server file: the default one serves the pages; `bunx alxia-react-router reveal` writes it out to customise |
|
|
33
33
|
|
|
34
34
|
The heart of the `api` project, its route and hook (the whole file, with
|
|
35
35
|
its imports and schemas, is in the [guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md#the-api-template)):
|
|
@@ -53,13 +53,17 @@ export const app = alxia()
|
|
|
53
53
|
|
|
54
54
|
## Docker
|
|
55
55
|
|
|
56
|
-
Both projects build into an image as they are written
|
|
57
|
-
|
|
56
|
+
Both projects build into an image as they are written: built on
|
|
57
|
+
`oven/bun:1`, run on `oven/bun:1-alpine` as the non-root `bun` user, an
|
|
58
|
+
image of about 130 MB. Each `Dockerfile`
|
|
59
|
+
builds in a stage of its own, and the image holds the build output alone,
|
|
60
|
+
no `node_modules`:
|
|
58
61
|
|
|
59
|
-
- `api`:
|
|
60
|
-
|
|
61
|
-
- `react-router`:
|
|
62
|
-
`
|
|
62
|
+
- `api`: `bun run build` bundles `src/server.ts` and its dependencies into
|
|
63
|
+
`dist/server.js`; the image holds `dist/` and runs `bun --no-install dist/server.js`.
|
|
64
|
+
- `react-router`: `bun run build`, every dependency bundled into
|
|
65
|
+
`build/server/index.js` by `@alxia/react-router`'s plugin; the image
|
|
66
|
+
holds `build/` and runs `bun --no-install build/server/index.js`.
|
|
63
67
|
|
|
64
68
|
```sh
|
|
65
69
|
cd my-api
|
|
@@ -76,7 +80,11 @@ docker run -p 3000:3000 my-site
|
|
|
76
80
|
Commit the `bun.lock` that `bun install` wrote: the image installs from it
|
|
77
81
|
with `--frozen-lockfile`. The
|
|
78
82
|
[guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md#docker)
|
|
79
|
-
has the stages
|
|
83
|
+
has the stages, and
|
|
84
|
+
[troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/create/docs/troubleshooting.md#error-cannot-find-package--from-appdistserverjs)
|
|
85
|
+
what to do for a dependency that cannot be bundled, and for a
|
|
86
|
+
[native addon built for glibc alone](https://github.com/softistx/alxia/blob/develop/packages/create/docs/troubleshooting.md#error--is-linked-against-glibc-dt_needed-libmso6-but-this-bun-build-uses-musl),
|
|
87
|
+
which does not load on Alpine.
|
|
80
88
|
|
|
81
89
|
## Options
|
|
82
90
|
|
package/docs/guide.md
CHANGED
|
@@ -67,10 +67,10 @@ my-api/
|
|
|
67
67
|
├── src/
|
|
68
68
|
│ ├── app.ts the app, and its type
|
|
69
69
|
│ ├── app.spec.ts bun test: app.request() and @alxia/client
|
|
70
|
-
│ └── server.ts app.listen(PORT)
|
|
70
|
+
│ └── server.ts app.listen(PORT), stopped on SIGTERM
|
|
71
71
|
├── package.json
|
|
72
72
|
├── tsconfig.json
|
|
73
|
-
├── Dockerfile
|
|
73
|
+
├── Dockerfile bun run build, then dist/ alone, on oven/bun:1-alpine
|
|
74
74
|
├── .dockerignore
|
|
75
75
|
├── .env.example PORT and API_KEY, for a .env Bun loads
|
|
76
76
|
├── .gitignore
|
|
@@ -139,12 +139,20 @@ The scripts:
|
|
|
139
139
|
| `bun dev` | `bun --watch src/server.ts`: restarted on every change, on `PORT` or 3000 |
|
|
140
140
|
| `bun test` | the spec |
|
|
141
141
|
| `bun run typecheck` | `tsc --noEmit` |
|
|
142
|
-
| `bun run build` | `bun build src/server.ts --target=bun --outdir=dist`: one file, its dependencies bundled |
|
|
143
|
-
| `bun start` | `bun
|
|
142
|
+
| `bun run build` | `bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked`: one file, its dependencies bundled |
|
|
143
|
+
| `bun start` | `bun dist/server.js`: the build, after `bun run build` |
|
|
144
144
|
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
`start` runs what `build` wrote, as production and the image do:
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
bun run build && bun start
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`dist/server.js` holds every dependency, so it runs on a host that has Bun
|
|
152
|
+
and no `node_modules`. It is minified, and `dist/server.js.map` beside it
|
|
153
|
+
is linked from it: Bun reads the map, so a stack trace names the lines of
|
|
154
|
+
`src/`. `bun dev` and `bun test` run the TypeScript as it is, with no
|
|
155
|
+
build.
|
|
148
156
|
|
|
149
157
|
Bun loads `.env` on every command. `.env.example` names the two variables
|
|
150
158
|
the app reads, `PORT` (3000 by default) and `API_KEY` (`dev-key` by
|
|
@@ -222,19 +230,56 @@ goes from there.
|
|
|
222
230
|
|
|
223
231
|
## Docker
|
|
224
232
|
|
|
225
|
-
Each project's `Dockerfile`
|
|
226
|
-
image,
|
|
233
|
+
Each project's `Dockerfile` builds it on Bun's official `oven/bun:1`
|
|
234
|
+
image, Debian's, and runs it on `oven/bun:1-alpine`, as that image's
|
|
235
|
+
non-root `bun` user (uid 1000). The installs are
|
|
227
236
|
`--frozen-lockfile`, from the `bun.lock` the command's `bun install`
|
|
228
237
|
wrote: commit it.
|
|
229
238
|
|
|
239
|
+
Each one builds in a stage of its own, and the image holds the build
|
|
240
|
+
output alone: no `node_modules`, no sources. The dependencies are inside
|
|
241
|
+
the bundle, so the image is the base image and a few hundred kilobytes to
|
|
242
|
+
a few megabytes: about 130 MB, where the same build on `oven/bun:1` is
|
|
243
|
+
about 345 MB.
|
|
244
|
+
|
|
245
|
+
The build stages stay on Debian: the tools a build runs, Vite's,
|
|
246
|
+
Tailwind's and any package's install script, are tried on glibc first,
|
|
247
|
+
and Alpine saves nothing there, since the stage is not shipped. What is
|
|
248
|
+
shipped is JavaScript, which Bun runs the same on musl. A native addon is
|
|
249
|
+
the exception: one built for glibc alone cannot load on Alpine, and the
|
|
250
|
+
final stage goes back to `oven/bun:1`
|
|
251
|
+
([troubleshooting](troubleshooting.md#error--is-linked-against-glibc-dt_needed-libmso6-but-this-bun-build-uses-musl)). A dependency that cannot be bundled is kept external and
|
|
252
|
+
copied in: see
|
|
253
|
+
[troubleshooting](troubleshooting.md#error-cannot-find-package--from-appdistserverjs).
|
|
254
|
+
|
|
230
255
|
### `api`
|
|
231
256
|
|
|
232
|
-
Two stages:
|
|
233
|
-
`bun install --frozen-lockfile
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
Two stages: every dependency, installed with
|
|
258
|
+
`bun install --frozen-lockfile`, then `bun run build`, which writes
|
|
259
|
+
`dist/server.js` and its source map; then an image with `dist/` alone,
|
|
260
|
+
running `bun --no-install dist/server.js`, `start`'s command with
|
|
261
|
+
Bun's `--no-install` (not create-alxia's option of the same name), so that a package missing from the bundle fails at
|
|
262
|
+
startup rather than being fetched from npm, written out so that Bun
|
|
263
|
+
is the container's process. `src/server.ts` stops the app on `SIGTERM`:
|
|
264
|
+
as process 1, Bun would otherwise ignore it, and `docker stop` would wait.
|
|
265
|
+
|
|
266
|
+
```dockerfile
|
|
267
|
+
FROM oven/bun:1 AS build
|
|
268
|
+
WORKDIR /app
|
|
269
|
+
COPY package.json bun.lock* bunfig.toml* ./
|
|
270
|
+
RUN bun install --frozen-lockfile
|
|
271
|
+
COPY . .
|
|
272
|
+
RUN bun run build
|
|
273
|
+
|
|
274
|
+
FROM oven/bun:1-alpine
|
|
275
|
+
WORKDIR /app
|
|
276
|
+
ENV NODE_ENV=production
|
|
277
|
+
COPY --from=build /app/dist ./dist
|
|
278
|
+
USER bun
|
|
279
|
+
EXPOSE 3000
|
|
280
|
+
CMD ["bun", "--no-install", "dist/server.js"]
|
|
281
|
+
```
|
|
282
|
+
|
|
238
283
|
`.dockerignore` keeps `node_modules`, `dist`, `.env`, the README and the
|
|
239
284
|
specs out of the context.
|
|
240
285
|
|
|
@@ -249,11 +294,15 @@ default is for development.
|
|
|
249
294
|
|
|
250
295
|
### `react-router`
|
|
251
296
|
|
|
252
|
-
|
|
253
|
-
`
|
|
254
|
-
`
|
|
255
|
-
|
|
256
|
-
|
|
297
|
+
Two stages: every dependency and `bun run build`, then an image with
|
|
298
|
+
`build/` alone, running `bun --no-install build/server/index.js`,
|
|
299
|
+
`start`'s command with `--no-install`.
|
|
300
|
+
`@alxia/react-router`'s plugin bundles every package into
|
|
301
|
+
`build/server/index.js` under `react-router build`, so `build/` needs no
|
|
302
|
+
`node_modules`
|
|
303
|
+
([Self-contained](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#self-contained)).
|
|
304
|
+
`.dockerignore` keeps `node_modules`, `build` and `.react-router` out of
|
|
305
|
+
the context.
|
|
257
306
|
|
|
258
307
|
```sh
|
|
259
308
|
cd my-site
|
package/docs/roadmap.md
CHANGED
|
@@ -30,12 +30,26 @@ Nothing scheduled yet.
|
|
|
30
30
|
|
|
31
31
|
### Next release
|
|
32
32
|
|
|
33
|
+
- **The images run on Alpine.** Every `Dockerfile`'s final stage is
|
|
34
|
+
`oven/bun:1-alpine`, the build stages staying on `oven/bun:1`: each
|
|
35
|
+
image is about 130 MB, where it was about 345 MB. The `api` project
|
|
36
|
+
stops on `SIGTERM`, so `docker stop` no longer waits.
|
|
37
|
+
|
|
38
|
+
### 0.1.3
|
|
39
|
+
|
|
40
|
+
- **Every `Dockerfile` builds, and the image holds the build alone.** The
|
|
41
|
+
`api` template's builds `dist/server.js`, bundled, minified and source
|
|
42
|
+
mapped, and runs it with no `node_modules` and no `src/`; `start` runs
|
|
43
|
+
`dist/server.js` after `bun run build`. The `react-router` template's
|
|
44
|
+
copies `build/` alone, which `@alxia/react-router`'s plugin now bundles
|
|
45
|
+
whole. Each image is about 50 MB (api) and 150 MB (react-router)
|
|
46
|
+
smaller.
|
|
47
|
+
|
|
48
|
+
### 0.1.2
|
|
49
|
+
|
|
33
50
|
- **The `api` template is files, copied, with a `Dockerfile`.** It ships
|
|
34
51
|
under `templates/api/` and is copied as `react-router`'s is. New in it:
|
|
35
|
-
a `Dockerfile` on `oven/bun:1`
|
|
36
|
-
dependencies and runs `src/server.ts` as the non-root `bun` user,
|
|
37
|
-
`.dockerignore`, `.env.example`, and `start` running the source with no
|
|
38
|
-
build first.
|
|
52
|
+
a `Dockerfile` on `oven/bun:1`, `.dockerignore` and `.env.example`.
|
|
39
53
|
|
|
40
54
|
### 0.1.1
|
|
41
55
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -36,6 +36,9 @@ nothing — the symptom.
|
|
|
36
36
|
**After**
|
|
37
37
|
|
|
38
38
|
- [`error: lockfile had changes, but lockfile is frozen`](#error-lockfile-had-changes-but-lockfile-is-frozen)
|
|
39
|
+
- [`error: Module not found "dist/server.js"`](#error-module-not-found-distserverjs)
|
|
40
|
+
- [`error: Cannot find package '…' from '/app/dist/server.js'`](#error-cannot-find-package--from-appdistserverjs)
|
|
41
|
+
- [`error: … is linked against glibc (DT_NEEDED libm.so.6), but this Bun build uses musl.`](#error--is-linked-against-glibc-dt_needed-libmso6-but-this-bun-build-uses-musl)
|
|
39
42
|
- [The project's `@alxia/*` are older than npm's latest](#the-projects-alxia-are-older-than-npms-latest)
|
|
40
43
|
|
|
41
44
|
## Before it runs
|
|
@@ -242,8 +245,7 @@ project is complete; only `node_modules` is missing.
|
|
|
242
245
|
### `error: lockfile had changes, but lockfile is frozen`
|
|
243
246
|
|
|
244
247
|
**When:** `docker build` in a project stops at
|
|
245
|
-
`RUN bun install --frozen-lockfile
|
|
246
|
-
`RUN bun install --frozen-lockfile --production` (`api`).
|
|
248
|
+
`RUN bun install --frozen-lockfile`.
|
|
247
249
|
|
|
248
250
|
**Why:** the project's `Dockerfile` installs exactly what `bun.lock`
|
|
249
251
|
records, and `package.json` now asks for something it does not: a
|
|
@@ -254,6 +256,105 @@ behind in a clone where `package.json` moved on without it.
|
|
|
254
256
|
traps of the `react-router` image, a write refused to the `bun` user among them, are in
|
|
255
257
|
[`@alxia/react-router`'s troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#eacces-permission-denied-open-app).
|
|
256
258
|
|
|
259
|
+
### `error: Module not found "dist/server.js"`
|
|
260
|
+
|
|
261
|
+
**When:** `bun start` in an `api` project that was never built, or whose
|
|
262
|
+
`dist/` was deleted.
|
|
263
|
+
|
|
264
|
+
**Why:** `start` runs the build, `bun dist/server.js`, as the image does;
|
|
265
|
+
it no longer runs `src/server.ts`. `bun dev` runs the sources.
|
|
266
|
+
|
|
267
|
+
**Fix:** build first:
|
|
268
|
+
|
|
269
|
+
```sh
|
|
270
|
+
bun run build && bun start
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `error: Cannot find package '…' from '/app/dist/server.js'`
|
|
274
|
+
|
|
275
|
+
The container stops at startup; in the `react-router` image the path is
|
|
276
|
+
`/app/build/server/index.js`. For a package loaded with `require`, Bun
|
|
277
|
+
writes `Cannot find module '…'`. Or the server starts and a request fails
|
|
278
|
+
with `ENOENT: no such file or directory`.
|
|
279
|
+
|
|
280
|
+
**When:** the image holds the build alone, `dist/` or `build/`, as the
|
|
281
|
+
`Dockerfile` copies it, and a dependency is not inside the bundle: one
|
|
282
|
+
the build was told to leave external, or one that cannot be bundled, a
|
|
283
|
+
native addon (a `.node` file) or a package that reads files of its own
|
|
284
|
+
folder at runtime.
|
|
285
|
+
|
|
286
|
+
The image runs `bun --no-install`, Bun's flag, not create-alxia's
|
|
287
|
+
option of the same name: without the flag, Bun finds no
|
|
288
|
+
`node_modules` and fetches the missing package from npm at startup, at
|
|
289
|
+
whatever version npm has, instead of failing.
|
|
290
|
+
|
|
291
|
+
**Why:** the image has no `node_modules`. `bun run build` bundles every
|
|
292
|
+
dependency into one file, and only what it leaves out must be installed
|
|
293
|
+
beside it.
|
|
294
|
+
|
|
295
|
+
**Fix:** mark the package external, and give the image the production
|
|
296
|
+
dependencies. In `api`, in the `build` script:
|
|
297
|
+
|
|
298
|
+
```json
|
|
299
|
+
"build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked --external sharp"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
In `react-router`, in `vite.config.ts`:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
export default defineConfig({
|
|
306
|
+
ssr: { external: ['sharp'] },
|
|
307
|
+
plugins: [tailwindcss(), reactRouter(), alxia()],
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Then in the `Dockerfile`, a stage for the production dependencies, copied
|
|
312
|
+
beside the build:
|
|
313
|
+
|
|
314
|
+
```dockerfile
|
|
315
|
+
# Before the build stage.
|
|
316
|
+
FROM oven/bun:1 AS production-dependencies
|
|
317
|
+
WORKDIR /app
|
|
318
|
+
COPY package.json bun.lock* bunfig.toml* ./
|
|
319
|
+
RUN bun install --frozen-lockfile --production
|
|
320
|
+
|
|
321
|
+
# In the final stage, before USER bun.
|
|
322
|
+
COPY --from=production-dependencies /app/node_modules ./node_modules
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`grep '^import' dist/server.js` (or `build/server/index.js`) lists what
|
|
326
|
+
the bundle still imports: Node's and Bun's modules, and the packages left
|
|
327
|
+
external.
|
|
328
|
+
|
|
329
|
+
### `error: … is linked against glibc (DT_NEEDED libm.so.6), but this Bun build uses musl.`
|
|
330
|
+
|
|
331
|
+
The container stops at startup, or at the first request that loads the
|
|
332
|
+
package, with the path of a `.node` file in `node_modules` and
|
|
333
|
+
`code: "ERR_DLOPEN_FAILED"`. The library named after `DT_NEEDED` is
|
|
334
|
+
whichever one the addon links first: `libc.so.6`, `libstdc++.so.6` or
|
|
335
|
+
another; the fix is the same.
|
|
336
|
+
|
|
337
|
+
**When:** a package is kept external and installed beside the build, as
|
|
338
|
+
the previous entry says, and it loads a native addon built for glibc
|
|
339
|
+
alone.
|
|
340
|
+
|
|
341
|
+
**Why:** the final stage is `oven/bun:1-alpine`, whose C library is
|
|
342
|
+
musl. The bundle is JavaScript, which runs the same there, but a `.node`
|
|
343
|
+
file is compiled against one C library, and one compiled for glibc
|
|
344
|
+
cannot load on musl, even with `gcompat`. A package that publishes a
|
|
345
|
+
musl build too, as `sharp` does, works: `bun install`, `--production` too,
|
|
346
|
+
puts both variants in `node_modules`, and the package picks musl's.
|
|
347
|
+
|
|
348
|
+
**Fix:** run the final stage on Debian's image, `oven/bun:1`, which
|
|
349
|
+
holds glibc. The build stages stay as they are:
|
|
350
|
+
|
|
351
|
+
```dockerfile
|
|
352
|
+
# The final stage.
|
|
353
|
+
FROM oven/bun:1
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The image is about 200 MB larger.
|
|
357
|
+
|
|
257
358
|
### The project's `@alxia/*` are older than npm's latest
|
|
258
359
|
|
|
259
360
|
**Symptom:** a fresh project declares `@alxia/core` at `^0.3.4` while npm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alxia/create",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Start an alxia app: `bun create @alxia` writes an API with Zod, or React Router's official template served by alxia",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@alxia/client": "^0.2.1",
|
|
43
43
|
"@alxia/core": "^0.3.1",
|
|
44
|
-
"@alxia/react-router": "^0.
|
|
44
|
+
"@alxia/react-router": "^0.4.0",
|
|
45
45
|
"@types/bun": "^1.4.2",
|
|
46
46
|
"zod": "^4.6.5"
|
|
47
47
|
}
|
package/templates/api/Dockerfile
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
# Bun
|
|
2
|
-
#
|
|
3
|
-
# supports.
|
|
1
|
+
# Bun bundles the app and its dependencies into dist/server.js, and the
|
|
2
|
+
# image holds dist/ alone: no node_modules, no src/. Pinned to Bun 1, the
|
|
3
|
+
# major alxia supports.
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
FROM oven/bun:1 AS
|
|
5
|
+
# Every dependency, then bun run build: dist/server.js and its source map.
|
|
6
|
+
FROM oven/bun:1 AS build
|
|
7
7
|
WORKDIR /app
|
|
8
8
|
COPY package.json bun.lock* bunfig.toml* ./
|
|
9
|
-
RUN bun install --frozen-lockfile
|
|
9
|
+
RUN bun install --frozen-lockfile
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN bun run build
|
|
10
12
|
|
|
11
|
-
# The
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
13
|
+
# The bundle, run as Bun's own non-root user with the start script's
|
|
14
|
+
# command, written out so that Bun is the container's process, and
|
|
15
|
+
# --no-install: with no node_modules, Bun would otherwise fetch a package
|
|
16
|
+
# the build left out from the registry at startup, where this fails. The
|
|
17
|
+
# server listens on PORT (3000); set API_KEY. On Alpine, about 200 MB
|
|
18
|
+
# lighter: the bundle is JavaScript, which runs the same on musl. A native
|
|
19
|
+
# addon built for glibc alone needs oven/bun:1 here.
|
|
20
|
+
FROM oven/bun:1-alpine
|
|
15
21
|
WORKDIR /app
|
|
16
22
|
ENV NODE_ENV=production
|
|
17
|
-
COPY
|
|
18
|
-
COPY --from=production-dependencies /app/node_modules ./node_modules
|
|
19
|
-
COPY src ./src
|
|
23
|
+
COPY --from=build /app/dist ./dist
|
|
20
24
|
USER bun
|
|
21
25
|
EXPOSE 3000
|
|
22
|
-
CMD ["bun", "
|
|
26
|
+
CMD ["bun", "--no-install", "dist/server.js"]
|
package/templates/api/README.md
CHANGED
|
@@ -39,21 +39,37 @@ bun run typecheck
|
|
|
39
39
|
|
|
40
40
|
## Build
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
`bun run build` bundles the server and its dependencies into one file,
|
|
43
|
+
`dist/server.js`, minified, with its source map beside it, and `bun start`
|
|
44
|
+
runs it: what production and the image run.
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
|
-
bun run build # dist/server.js
|
|
48
|
-
bun dist/server.js
|
|
47
|
+
bun run build # dist/server.js and dist/server.js.map
|
|
48
|
+
bun start # bun dist/server.js
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
`bun start` before any build fails with `Module not found
|
|
52
|
+
"dist/server.js"`: build first. `dist/` needs Bun and nothing else, no
|
|
53
|
+
`node_modules`. Bun reads the
|
|
54
|
+
source map, so a stack trace names the lines of `src/`. `bun dev` and
|
|
55
|
+
`bun test` run the TypeScript as it is, with no build.
|
|
56
|
+
|
|
57
|
+
A dependency that cannot be bundled, a native addon, is left out with
|
|
58
|
+
`--external <name>` in the `build` script, and must then be installed
|
|
59
|
+
beside `dist/`.
|
|
60
|
+
|
|
51
61
|
## Docker
|
|
52
62
|
|
|
53
|
-
The `Dockerfile`
|
|
54
|
-
|
|
55
|
-
`--frozen-lockfile`, from the `bun.lock`
|
|
56
|
-
it
|
|
63
|
+
The `Dockerfile` builds in a stage of its own, on `oven/bun:1`, and runs
|
|
64
|
+
on `oven/bun:1-alpine`. The build stage
|
|
65
|
+
installs every dependency with `--frozen-lockfile`, from the `bun.lock`
|
|
66
|
+
that `bun install` wrote (commit it), and runs `bun run build`. The image
|
|
67
|
+
holds `dist/` alone, no `node_modules` and no `src/`, and runs
|
|
68
|
+
`bun --no-install dist/server.js` as its non-root `bun` user: a package
|
|
69
|
+
missing from the bundle fails at startup instead of being fetched from
|
|
70
|
+
npm. `src/server.ts` stops the app on `SIGTERM`, so `docker stop` is
|
|
71
|
+
immediate. A native addon built for glibc alone does not load on
|
|
72
|
+
Alpine: put the final stage back on `oven/bun:1`.
|
|
57
73
|
|
|
58
74
|
```sh
|
|
59
75
|
docker build -t my-api .
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "bun --watch src/server.ts",
|
|
7
|
-
"build": "bun build src/server.ts --target=bun --outdir=dist",
|
|
8
|
-
"start": "bun
|
|
7
|
+
"build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked",
|
|
8
|
+
"start": "bun dist/server.js",
|
|
9
9
|
"test": "bun test",
|
|
10
10
|
"typecheck": "tsc --noEmit"
|
|
11
11
|
},
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { app } from './app';
|
|
2
2
|
|
|
3
|
+
// Stop as the platform asks. In a container Bun is process 1, which a
|
|
4
|
+
// signal with no handler does not stop: `docker stop` would wait.
|
|
5
|
+
for (const signal of ['SIGINT', 'SIGTERM'] as const) {
|
|
6
|
+
process.once(signal, () => {
|
|
7
|
+
void app.stop().then(
|
|
8
|
+
() => process.exit(0),
|
|
9
|
+
(error: unknown) => {
|
|
10
|
+
console.error(error);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
},
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
const server = app.listen(Number(Bun.env['PORT'] ?? 3000));
|
|
4
18
|
console.log(`listening on ${server.url}`);
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
# Bun builds the app and
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
FROM oven/bun:1 AS production-dependencies
|
|
6
|
-
WORKDIR /app
|
|
7
|
-
COPY package.json bun.lock* bunfig.toml* ./
|
|
8
|
-
RUN bun install --frozen-lockfile --production
|
|
1
|
+
# Bun builds the app, and the image holds build/ alone: alxia's plugin
|
|
2
|
+
# bundles every dependency into build/server/index.js, alxia's server, which
|
|
3
|
+
# runs on Bun with no node_modules. Pinned to Bun 1, the major alxia
|
|
4
|
+
# supports.
|
|
9
5
|
|
|
10
6
|
# Every dependency, then react-router build: build/client and
|
|
11
7
|
# build/server/index.js.
|
|
@@ -16,14 +12,17 @@ RUN bun install --frozen-lockfile
|
|
|
16
12
|
COPY . .
|
|
17
13
|
RUN bun run build
|
|
18
14
|
|
|
19
|
-
# The build
|
|
20
|
-
#
|
|
21
|
-
|
|
15
|
+
# The build, run as Bun's own non-root user with the start script's
|
|
16
|
+
# command, and --no-install: with no node_modules, Bun would otherwise
|
|
17
|
+
# fetch a package the build left out from the registry at startup,
|
|
18
|
+
# where this fails.
|
|
19
|
+
# The server listens on PORT (3000) and HOST (0.0.0.0). On Alpine, about
|
|
20
|
+
# 200 MB lighter: build/ is JavaScript, which runs the same on musl. A
|
|
21
|
+
# native addon built for glibc alone needs oven/bun:1 here.
|
|
22
|
+
FROM oven/bun:1-alpine
|
|
22
23
|
WORKDIR /app
|
|
23
24
|
ENV NODE_ENV=production
|
|
24
|
-
COPY package.json ./
|
|
25
|
-
COPY --from=production-dependencies /app/node_modules ./node_modules
|
|
26
25
|
COPY --from=build /app/build ./build
|
|
27
26
|
USER bun
|
|
28
27
|
EXPOSE 3000
|
|
29
|
-
CMD ["bun", "build/server/index.js"]
|
|
28
|
+
CMD ["bun", "--no-install", "build/server/index.js"]
|
|
@@ -66,7 +66,7 @@ The containerized application can be deployed to any platform that supports Dock
|
|
|
66
66
|
|
|
67
67
|
### DIY Deployment
|
|
68
68
|
|
|
69
|
-
The build is production-ready: `bun run start` runs `build/server/index.js` on Bun
|
|
69
|
+
The build is production-ready and self-contained: `bun run start` runs `build/server/index.js` on Bun, with every dependency bundled into it, so `build/` needs no `node_modules`.
|
|
70
70
|
|
|
71
71
|
Make sure to deploy the output of `bun run build`
|
|
72
72
|
|