diff --git a/README.md b/README.md index 58cdfe323a..2f013a8390 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ docker compose -f docker/docker-compose.yml --profile gateway down ### Launcher Mode (Web Console) The `launcher` image includes all three binaries (`picoclaw`, `picoclaw-launcher`, `picoclaw-launcher-tui`) and starts the web console by default, which provides a browser-based UI for configuration and chat. +It also ships with a Node.js runtime so npm-based MCP servers and scripts can run inside the launcher container without rebuilding the image. ```bash docker compose -f docker/docker-compose.yml --profile launcher up -d diff --git a/docker/Dockerfile.goreleaser.launcher b/docker/Dockerfile.goreleaser.launcher index 5d65576f70..cdfe147a23 100644 --- a/docker/Dockerfile.goreleaser.launcher +++ b/docker/Dockerfile.goreleaser.launcher @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM node:24-alpine3.23 ARG TARGETPLATFORM diff --git a/docker/launcher_dockerfile_test.go b/docker/launcher_dockerfile_test.go new file mode 100644 index 0000000000..f446e3c56c --- /dev/null +++ b/docker/launcher_dockerfile_test.go @@ -0,0 +1,25 @@ +package docker + +import ( + "os" + "strings" + "testing" +) + +func TestLauncherDockerfileIncludesNodeRuntime(t *testing.T) { + data, err := os.ReadFile("Dockerfile.goreleaser.launcher") + if err != nil { + t.Fatalf("read Dockerfile.goreleaser.launcher: %v", err) + } + + content := string(data) + if !strings.Contains(content, "FROM node:") { + t.Fatalf("launcher Dockerfile should use a Node.js runtime base image, got:\n%s", content) + } + if !strings.Contains(content, "COPY $TARGETPLATFORM/picoclaw-launcher /usr/local/bin/picoclaw-launcher") { + t.Fatal("launcher Dockerfile should still copy the launcher binary") + } + if !strings.Contains(content, "ENTRYPOINT [\"picoclaw-launcher\"]") { + t.Fatal("launcher Dockerfile should keep picoclaw-launcher as the entrypoint") + } +}