diff --git a/docker/Dockerfile.local b/docker/Dockerfile.local new file mode 100644 index 0000000..84b3ab8 --- /dev/null +++ b/docker/Dockerfile.local @@ -0,0 +1,36 @@ +# Local-testing variant of docker/Dockerfile. +# Instead of installing the published @flowfuse/device-agent from npm, it +# installs a tarball built from the local working copy via `npm pack`. +# +# Build context MUST be the device-agent repo root so the .tgz is reachable: +# cd packages/device-agent +# npm pack +# docker build -f docker/Dockerfile.local -t flowfuse/device-agent:local . +ARG NODE_VERSION=24 +FROM node:${NODE_VERSION}-alpine + +ARG FF_UID=2000 +ARG FF_GID=2000 + +RUN apk add --no-cache --virtual buildtools build-base linux-headers udev python3 openssl + +RUN addgroup -g ${FF_GID} -S flowfuse \ + && adduser -u ${FF_UID} -S -G flowfuse -h /opt/flowfuse-device flowfuse \ + && mkdir -p /opt/flowfuse-device \ + && chown -R "${FF_UID}":"${FF_GID}" /opt/flowfuse-device + +# Copy the locally-built package tarball into the image and install it globally. +COPY flowfuse-device-agent-*.tgz /tmp/device-agent.tgz +RUN npm install -g npm \ + && npm config set cache /opt/flowfuse-device/.npm --global \ + && npm install -g /tmp/device-agent.tgz --omit=dev \ + && rm -f /tmp/device-agent.tgz \ + && chown -R ${FF_UID}:${FF_GID} /opt/flowfuse-device + +EXPOSE 1880 + +ENV HOME=/opt/flowfuse-device + +USER flowfuse + +CMD ["flowfuse-device-agent"] diff --git a/docker/build-local.md b/docker/build-local.md new file mode 100644 index 0000000..31131ef --- /dev/null +++ b/docker/build-local.md @@ -0,0 +1,51 @@ +## Local feature branch testing + +The image published to a registry installs the released `@flowfuse/device-agent` +package from npm. When testing **local code changes** (e.g. on a feature branch), +you can use the provided helper script to build an image from your branch instead. + +The script uses [`Dockerfile.local`](./Dockerfile.local), which packages the local +source with `npm pack` and installs that tarball — mirroring how the production +image installs the published package, but with your changes. + +### Building + +From the device-agent repo root, run the script: + +```bash +# Linux / macOS / Windows Subsystem for Linux (WSL) +# optional: make the script executable: +sudo chmod +x ./docker/build-local.sh +# then run: +./docker/build-local.sh +``` + +The script will: + +- remove any stale `flowfuse-device-agent-*.tgz` +- run `npm pack` to package the local working copy +- build and tag the image based on the current git branch: + - on `main` → `flowfuse/device-agent:local-build` + - on any other branch → `flowfuse/device-agent:-local-build` + (branch lowercased, illegal tag characters replaced with `-`) + +You can override the defaults: + +```bash +IMAGE_NAME=myorg/device-agent ./docker/build-local.sh # override repo name +TAG=custom-tag ./docker/build-local.sh # override the whole tag +``` + +### Running + +Run exactly as the published image, just using the locally-built tag and +mounting your `device.yml`: + +```bash +docker run --rm -it -v /path/to/device.yml:/opt/flowfuse-device/device.yml -p 1880:1880 flowfuse/device-agent:local-build +``` + +Re-running a build script reassigns the tag to the freshly built image; the +previous build becomes a dangling (``) image. A running container is not +updated automatically — stop it and `docker run` again to pick up a rebuild. +Clean up old dangling images occasionally with `docker image prune -f`. diff --git a/docker/build-local.sh b/docker/build-local.sh new file mode 100644 index 0000000..a10debb --- /dev/null +++ b/docker/build-local.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Builds a local-testing image of the device-agent from the current working copy. +# +# - clears any stale flowfuse-device-agent-*.tgz +# - runs `npm pack` to package the local code +# - tags the image based on the current git branch: +# main -> flowfuse/device-agent:local-build +# -> flowfuse/device-agent:-local-build +# +# Usage (from anywhere): +# ./docker/build-local.sh +# IMAGE_NAME=myorg/device-agent ./docker/build-local.sh # override repo name +# TAG=custom-tag ./docker/build-local.sh # override the whole tag + +# Fail fast: -e exits on any command error, -u errors on unset variables, +# -o pipefail makes a pipeline fail if any stage (not just the last) fails. +set -euo pipefail + +# Generate a default image name and tag if not supplied by the caller. +IMAGE_NAME="${IMAGE_NAME:-flowfuse/device-agent}" +TAG="${TAG:-}" + +# Resolve the device-agent repo root (parent of this script's /docker dir) and work from there. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" +cd "$REPO_ROOT" + +# 1. Clear any old tarballs so only the fresh pack remains. +for f in flowfuse-device-agent-*.tgz; do + if [ -e "$f" ]; then + echo "Removing old pack: $f" + rm -f "$f" + fi +done + +# 2. Package the local working copy. +echo "Running npm pack..." +npm pack + +# 3. Work out the tag from the branch name (unless one was supplied). +if [ -z "$TAG" ]; then + branch="$(git rev-parse --abbrev-ref HEAD)" + if [ "$branch" = "main" ]; then + TAG="local-build" + else + # Sanitise: lowercase, and replace anything not allowed in a docker tag with '-' + safe_branch="$(echo "$branch" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_.-]/-/g')" + TAG="${safe_branch}-local-build" + fi +fi + +FULL_IMAGE="${IMAGE_NAME}:${TAG}" + +# 4. Build the image (context = repo root, so the .tgz is reachable by COPY). +echo "Building image: $FULL_IMAGE" +docker build -f docker/Dockerfile.local -t "$FULL_IMAGE" . + +echo "" +echo "Built $FULL_IMAGE" +echo "" +echo "Enter the following command to run the device-agent:" +echo " docker run --rm -it \\" +echo " -v /opt/flowfuse-device-docker-local/device.yml:/opt/flowfuse-device/device.yml \\" +echo " -p 1888:1880 \\" +echo " $FULL_IMAGE" +echo "" +echo "NOTE:" +echo "Entering the above command will run the device-agent on port 1888" +echo "using your local device.yml (edit as needed before hitting enter)"