Skip to content
Merged
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
44 changes: 44 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ jobs:
with:
file: ./VERSION
id: version
-
name: Read Node.js version - minor
uses: bfren/read-file@v2
with:
file: ./${{ matrix.node }}/overlay/tmp/NODE_MINOR
id: node_minor
-
name: Read image version
uses: bfren/read-file@v2
with:
file: ./VERSION_MAJOR
id: version_major
-
name: Read image version
uses: bfren/read-file@v2
with:
file: ./VERSION_MINOR
id: version_minor
-
name: Set up QEMU
uses: docker/setup-qemu-action@v4
Expand Down Expand Up @@ -79,9 +97,35 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: |
bfren/node:node${{ matrix.node }}-dev
bfren/node:node${{ matrix.node }}-${{ steps.version_major.outputs.contents }}-dev
bfren/node:node${{ matrix.node }}-${{ steps.version_minor.outputs.contents }}-dev
bfren/node:node${{ matrix.node }}-${{ steps.version.outputs.contents }}-dev
bfren/node:node${{ steps.node_minor.outputs.contents }}-dev
bfren/node:node${{ steps.node_minor.outputs.contents }}-${{ steps.version.outputs.contents }}-dev
quay.io/bfren/node:node${{ matrix.node }}-dev
quay.io/bfren/node:node${{ matrix.node }}-${{ steps.version_major.outputs.contents }}-dev
quay.io/bfren/node:node${{ matrix.node }}-${{ steps.version_minor.outputs.contents }}-dev
quay.io/bfren/node:node${{ matrix.node }}-${{ steps.version.outputs.contents }}-dev
quay.io/bfren/node:node${{ steps.node_minor.outputs.contents }}-dev
quay.io/bfren/node:node${{ steps.node_minor.outputs.contents }}-${{ steps.version.outputs.contents }}-dev
-
name: Build and push default
id: docker_build_default
if: matrix.node == '24'
uses: docker/build-push-action@v7
with:
context: .
file: ./${{ matrix.node }}/Dockerfile
build-args: |
BF_IMAGE=${{ env.REPOSITORY_NAME }}
BF_VERSION=${{ steps.version.outputs.contents }}
push: ${{ startsWith(github.ref, 'refs/heads/') }}
platforms: linux/amd64,linux/arm64
tags: |
bfren/node:dev
bfren/node:${{ steps.version.outputs.contents }}-dev
quay.io/bfren/node:dev
quay.io/bfren/node:${{ steps.version.outputs.contents }}-dev
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
2 changes: 1 addition & 1 deletion 16/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.16-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.16-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 17/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.15-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.15-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 18/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.18-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.18-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 19/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.17-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.17-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 20/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.20-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.20-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 21/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.20-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.20-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 22/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.22-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.22-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 23/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.22-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.22-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 24/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.24-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.24-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
2 changes: 1 addition & 1 deletion 26/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM quay.io/bfren/alpine-s6:alpine3.24-6.5.0
FROM quay.io/bfren/alpine-s6:alpine3.24-6.6.0

LABEL org.opencontainers.image.source="https://github.com/bfren/docker-node"

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../index/CLAUDE.md
143 changes: 135 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,146 @@
# Docker Node.js
# Node.js

![GitHub release (latest by date)](https://img.shields.io/github/v/release/bfren/docker-node) ![Docker Pulls](https://img.shields.io/endpoint?url=https%3A%2F%2Fbfren.dev%2Fdocker%2Fpulls%2Fnode) ![Docker Image Size](https://img.shields.io/endpoint?url=https%3A%2F%2Fbfren.dev%2Fdocker%2Fsize%2Fnode) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/bfren/docker-node/dev.yml?branch=main)

[Docker Repository](https://hub.docker.com/r/bfren/nginx-webdav) - [bfren ecosystem](https://github.com/bfren/docker)
[Docker Hub](https://hub.docker.com/r/bfren/node) - [Quay.io](https://quay.io/repository/bfren/node) - [bfren ecosystem](https://github.com/bfren/docker)

[Node.js](https://nodejs.org), with `npm` also installed.
## Introduction

## Contents
[Node.js](https://nodejs.org) and `npm`, running your app as a supervised service. It is built on [Alpine + S6](https://github.com/bfren/docker-alpine-s6), with Node.js from Alpine's packages - each Node.js version uses the newest Alpine release that has it.

* [Ports](#ports)
* [Licence / Copyright](#licence)
The image runs `/app/live/index.js` as the `node` user. Use it as the base for a Node.js app's image (as [Send](https://github.com/bfren/docker-send) does), or mount a ready-to-run app into it.

## Ports
## Quickstart

* 3000
With your app (including `node_modules`) in `./app`, and `index.js` listening on port 3000:

```yaml
services:
app:
image: quay.io/bfren/node:node24-4.2
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
volumes:
- ./app:/app/live
```

Run `docker compose up -d`, then open `http://127.0.0.1:3000`. Without your own app, a welcome page is shown.

Your app must listen on `0.0.0.0` (all interfaces), not `127.0.0.1` - otherwise it can't be reached through the published port.

## Features

* 🟩 Node.js 16 to 26 - choose with the image tag
* 📦 `npm` installed
* 🧭 Your app runs under S6 supervision as `node` (UID 1000), not root
* 🛑 The container stops if your app crashes, so Docker can restart it
* 🔐 `/app/live` is made read-only for the app on every start
* 🧪 Built-in tests

## Tags

Images are published to Docker Hub (`bfren/node`) and Quay.io (`quay.io/bfren/node`) for `linux/amd64` and `linux/arm64`.

| Tag | Example | Use when |
| ----------------- | ----------------- | ----------------------------------------------- |
| `nodeN` | `node24` | You want the latest image for a Node.js version |
| `nodeN-MAJOR` | `node24-4` | You want updates, but not breaking changes |
| `nodeN-MINOR` | `node24-4.2` | You want fixes only - **recommended** |
| `nodeN-VERSION` | `node24-4.2.6` | You want to pin this image exactly |
| `nodeN.M` | `node24.18` | You want an exact Node.js release |
| `nodeN.M-VERSION` | `node24.18-4.2.6` | You want to pin both |
| `latest`, `4.2.6` | | As above, on Node.js 24 |

| Node.js | Release | Alpine | Security support ends |
| ------- | ------- | ------ | --------------------- |
| 26 | 26.5.1 | 3.24 | 2029-04-30 (LTS) |
| 24 | 24.18.1 | 3.24 | 2028-04-30 (LTS) |
| 23 | 23.11.1 | 3.22 | ended 2025-06-01 |
| 22 | 22.23.2 | 3.22 | 2027-04-30 (LTS) |
| 21 | 21.7.3 | 3.20 | ended 2024-06-01 |
| 20 | 20.15.1 | 3.20 | ended 2026-04-30 |
| 19 | 19.7.0 | 3.17 | ended 2023-06-01 |
| 18 | 18.20.1 | 3.18 | ended 2025-04-30 |
| 17 | 17.9.0 | 3.15 | ended 2022-06-01 |
| 16 | 16.20.2 | 3.16 | ended 2023-09-11 |

Use an LTS version for anything long-lived - see the [Node.js release schedule](https://nodejs.org/en/about/previous-releases). Images for versions that have ended are still built, but get no Node.js fixes.

## Configuration

### Ports

| Port | Purpose |
| ------ | ----------------------------------------------------- |
| `3000` | Used by the welcome page - your app can use any port. |

### Volumes

| Volume | Purpose |
| ----------- | -------------------------------------- |
| `/app/live` | Your app - `index.js` is run on start. |

### Environment Variables

This image adds none of its own. See [Alpine + S6](https://github.com/bfren/docker-alpine-s6#environment-variables) for `BF_DEBUG`, `BF_TZ` and the other base image variables. Your app can read any variables you pass to the container.

## How It Works

On every start the Node.js and `npm` versions are written to the log, and everything in `/app/live` is given to the `node` user and made read-only (`0400` files, `0500` folders). Then `node /app/live/index.js` runs under S6 as `node`.

If your app crashes (exits with an error), the container stops - set a `restart` policy so Docker starts it again. If it exits cleanly, S6 starts it again inside the container.

The health check is the base image's, which checks cron is running - it doesn't check your app. To check your app, replace `/usr/bin/bf/bf-healthcheck` in your own image.

## Building on this image

Copy your app into `/app/live` and install its dependencies when the image is built:

```dockerfile
FROM quay.io/bfren/node:node24-4.2

COPY ./app /app/live
RUN cd /app/live && npm ci --omit=dev
```

`index.js` must be the entry point. If your app's entry point is elsewhere, add an `index.js` that loads it, e.g. `require('./dist/server.js');`.

## Testing

```sh
./test.nu # builds Node.js 24 and runs /test
./test.nu 20 # or any version directory
./test.nu --all # every version, in parallel
./run.sh 24 # builds and opens a shell
./generate-dockerfiles.sh # regenerates the Dockerfiles from Dockerfile.esh
```

`/test` runs this image's tests alongside the base images' tests: the app paths, the welcome page (valid JavaScript, listening on all interfaces), the installed Node.js and `npm` versions, and the service scripts.

## Troubleshooting

### The container stops straight after starting

Your app crashed. Check `docker logs <container>` for its error - often a missing `node_modules` folder, or no `/app/live/index.js`.

### My app can't write files (`EACCES`)

`/app/live` is read-only on purpose. Write data to another folder, and mount a volume there.

### The port is published but nothing answers

Your app is probably listening on `127.0.0.1` or `localhost` - change it to `0.0.0.0`.

## FAQs

### How do I run `npm install` in the container?

`docker exec <container> sh -c 'cd /app/live && npm install'` works, but installing when the image is built (see [Building on this image](#building-on-this-image)) is more reliable, and the new files are only given to `node` on the next start.

### Can I use a different entry file?

Not directly - `/app/live/index.js` is always run. Make it load your real entry point.

## Licence

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2.5
4.2.6
2 changes: 1 addition & 1 deletion generate-dockerfiles.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -euo pipefail

docker pull bfren/alpine

BASE_VERSION="6.5.0"
BASE_VERSION="6.6.0"
echo "Base: ${BASE_VERSION}"

NODE_VERSIONS="16 17 18 19 20 21 22 23 24 26"
Expand Down
4 changes: 2 additions & 2 deletions overlay/app/live/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Import http module
const http = require('http');

// Hosting values
const hostname = '127.0.0.1';
// Hosting values - listen on all interfaces so the port can be published
const hostname = '0.0.0.0';
const port = 3000;

// Create server
Expand Down
16 changes: 7 additions & 9 deletions overlay/etc/bf/init.d/10-env.nu
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use bf
use bf-node
bf env load

# Set environment variables
def main [] {
# app directories
let app = "/app"
let app_live = $"($app)/live"
bf env set NODE_APP $app
bf env set NODE_APP_LIVE $app_live
bf env set NODE_APP_PUBLISH $"($app)/publish"

# run file
bf env set NODE_RUN $"($app_live)/index.js"
# app directories and run file
let paths = bf-node app paths
bf env set NODE_APP $paths.app
bf env set NODE_APP_LIVE $paths.live
bf env set NODE_APP_PUBLISH $paths.publish
bf env set NODE_RUN $paths.run

# return nothing
return
Expand Down
11 changes: 11 additions & 0 deletions overlay/etc/nu/scripts/bf-node/app.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Paths used to run the Node.js app
export def paths []: nothing -> record {
let app = "/app"
let live = $"($app)/live"
{
app: $app
live: $live
publish: $"($app)/publish"
run: $"($live)/index.js"
}
}
1 change: 1 addition & 0 deletions overlay/etc/nu/scripts/bf-node/mod.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export use app.nu
38 changes: 38 additions & 0 deletions overlay/etc/nu/scripts/tests/app.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std/assert
use bf-node


#======================================================================================================================
# paths
#======================================================================================================================

export def paths__run_file_is_in_live_directory [] {
let paths = bf-node app paths

assert equal "/app/live" $paths.live
assert equal "/app/live/index.js" $paths.run
assert ($paths.run | str starts-with $paths.live)
}


#======================================================================================================================
# default app
#======================================================================================================================

export def default_app__exists [] {
assert ((bf-node app paths).run | path exists)
}

export def default_app__listens_on_all_interfaces [] {
# listening on 127.0.0.1 would make the published port unreachable
let source = open --raw (bf-node app paths).run

assert ($source =~ "hostname = '0.0.0.0'")
assert ($source =~ "port = 3000")
}

export def default_app__is_valid_javascript [] {
let result = ^node --check (bf-node app paths).run | complete

assert equal 0 $result.exit_code $result.stderr
}
Loading
Loading