From 39a10986863e3f9830ab463363483696b342abe3 Mon Sep 17 00:00:00 2001 From: reluc Date: Wed, 10 Dec 2025 16:31:38 +0100 Subject: [PATCH 1/2] feat: support build secrets with version 2 --- lib/docker.js | 7 +++++- lib/proto/secrets.proto | 19 ++++++++++++++++ lib/session.js | 25 ++++++++++++++++---- test/buildkit-secrets.Dockerfile | 5 ++++ test/docker.js | 39 ++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 lib/proto/secrets.proto create mode 100644 test/buildkit-secrets.Dockerfile diff --git a/lib/docker.js b/lib/docker.js index e3739dd..38abca5 100644 --- a/lib/docker.js +++ b/lib/docker.js @@ -287,6 +287,11 @@ Docker.prototype.buildImage = function(file, opts, callback) { delete optsf.options.authconfig; } + if (opts.secrets) { + // avoid sending secrets as parameters in the request. + delete optsf.options.secrets; + } + if (opts.cachefrom && Array.isArray(opts.cachefrom)) { optsf.options.cachefrom = JSON.stringify(opts.cachefrom); } @@ -301,7 +306,7 @@ Docker.prototype.buildImage = function(file, opts, callback) { function dialWithSession(callback) { if (opts?.version === "2") { - withSession(self, optsf.authconfig,(err, sessionId, done) => { + withSession(self, {auth: optsf.authconfig, secrets: opts.secrets },(err, sessionId, done) => { if (err) { return callback(err); } diff --git a/lib/proto/secrets.proto b/lib/proto/secrets.proto new file mode 100644 index 0000000..cd19f2a --- /dev/null +++ b/lib/proto/secrets.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package moby.buildkit.secrets.v1; + +option go_package = "secrets"; + +service Secrets{ + rpc GetSecret(GetSecretRequest) returns (GetSecretResponse); +} + + +message GetSecretRequest { + string ID = 1; + map annotations = 2; +} + +message GetSecretResponse { + bytes data = 1; +} \ No newline at end of file diff --git a/lib/session.js b/lib/session.js index 97ba8db..a535d3a 100644 --- a/lib/session.js +++ b/lib/session.js @@ -3,7 +3,7 @@ var grpc = require("@grpc/grpc-js"), path = require("path"), uuid = require("uuid").v4; -function withSession(docker, auth, handler) { +function withSession(docker, { auth, secrets }, handler) { const sessionId = uuid(); const opts = { @@ -32,11 +32,15 @@ function withSession(docker, auth, handler) { injector.injectConnection(socket); const pkg = protoLoader.loadSync( - path.resolve(__dirname, "proto", "auth.proto") + [ + path.resolve(__dirname, "proto", "auth.proto"), + path.resolve(__dirname, "proto", "secrets.proto"), + ] ); - const service = grpc.loadPackageDefinition(pkg); - server.addService(service.moby.filesync.v1.Auth.service, { + const services = grpc.loadPackageDefinition(pkg); + + server.addService(services.moby.filesync.v1.Auth.service, { Credentials({ request }, callback) { // We probably want to have the possibility to pass credentials per // hots. The correct one could be returned based on `request.Host` @@ -51,6 +55,19 @@ function withSession(docker, auth, handler) { }, }); + server.addService(services.moby.buildkit.secrets.v1.Secrets.service, { + GetSecret({ request }, callback) { + const found = secrets[request.ID]; + if (!found) { + return callback({message: "No secret found."}); + } + + callback(null, { + data: found, + }); + } + }) + function done() { server.forceShutdown(); socket.end(); diff --git a/test/buildkit-secrets.Dockerfile b/test/buildkit-secrets.Dockerfile new file mode 100644 index 0000000..ddaf49c --- /dev/null +++ b/test/buildkit-secrets.Dockerfile @@ -0,0 +1,5 @@ +FROM alpine + +RUN --mount=type=secret,id=s,target=/secret tail /secret + +CMD ["bash"] diff --git a/test/docker.js b/test/docker.js index e2758d0..eb87a13 100644 --- a/test/docker.js +++ b/test/docker.js @@ -295,6 +295,45 @@ describe("#docker", function () { handler ); }); + + it("should build image with buildKit and support secrets", function (done) { + this.timeout(60000); + const randomId = Math.random().toString(36).substring(7); + + function handler(err, stream) { + expect(err).to.be.null; + expect(stream).to.be.ok; + + stream.pipe(process.stdout, { + end: true, + }); + + stream.on("end", function () { + docker.getImage(randomId).inspect(function (err, image) { + expect(err).to.be.null; + expect(image).to.exist; + done(); + }); + }); + } + + docker.buildImage( + { + context: __dirname, + src: ["buildkit-secrets.Dockerfile"], + }, + { + dockerfile: "buildkit-secrets.Dockerfile", + secrets: { + s: Buffer.from("secret message.") + }, + version: "2", + t: randomId, + pull: "true", + }, + handler + ); + }); }); describe("#loadImage", function () { From fc8168dcccb0915a4633358118dc0fc28a140bef Mon Sep 17 00:00:00 2001 From: reluc Date: Wed, 10 Dec 2025 16:41:53 +0100 Subject: [PATCH 2/2] docs: add a short explaination on how to use secrets with build --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 44d0877..50b0ddc 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,25 @@ await new Promise((resolve, reject) => { // Build has finished ``` +### BuildKit secrets + +If the remote Docker daemon supports BuildKit, you can pass secrets to the build: + +```js +const buildOpts = { + t: 'myimage', + dockerfile: 'Dockerfile', + version: '2', + secrets: { + mysecret: Buffer.from('supersecret') + } +}; +docker.buildImage(tarStream, buildOpts, (err, stream) => { ... }); +``` + +This uses the session API and requires BuildKit. Secrets are delivered via a small in-session secret provider. + + ### Creating a container: