From 1b67c46ccff6779fddfab20445a1249fec3e5f9d Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sun, 23 Aug 2026 12:36:38 -0700 Subject: [PATCH] Send platform as a query parameter on container create /containers/create takes `platform` as a query parameter. Docker::Container.create forwarded only `name` to the query string and dumped everything else into the request body, so a caller asking for a platform got it placed in the body, where the daemon accepts and silently ignores it: QUERY={"name" => "dapi-plat"} requested linux/amd64 on an arm64 host -> got arm64 Nothing raised. The container simply came up on the wrong architecture, which is the worst way for this to fail. Pull the query keys out into CREATE_QUERY_KEYS and route both name and platform through it. With the fix the daemon actually sees the parameter, and asking for a platform the local image does not provide now reports that: image with reference alpine:latest was found but does not provide the specified platform (linux/arm64) Not a breaking change: `platform` previously had no effect wherever it was placed, so no caller can be relying on the old behaviour. --- lib/docker/container.rb | 9 +++++++-- spec/docker/container_spec.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/docker/container.rb b/lib/docker/container.rb index 3c0cc044..5065df83 100644 --- a/lib/docker/container.rb +++ b/lib/docker/container.rb @@ -324,10 +324,15 @@ def store_file(path, file_content) archive_in_stream("/", overwrite: true) { output_io.read } end + # Keys that /containers/create takes as query parameters rather than in the + # request body. The daemon silently ignores them if they are sent in the + # body, so a misplaced key means the option has no effect at all. + CREATE_QUERY_KEYS = %w[name platform].freeze + # Create a new Container. def self.create(opts = {}, conn = Docker.connection) - query = opts.select {|key| ['name', :name].include?(key) } - clean_opts = opts.reject {|key| ['name', :name].include?(key) } + query = opts.select { |key| CREATE_QUERY_KEYS.include?(key.to_s) } + clean_opts = opts.reject { |key| CREATE_QUERY_KEYS.include?(key.to_s) } resp = conn.post('/containers/create', query, :body => MultiJson.dump(clean_opts)) hash = Docker::Util.parse_json(resp) || {} new(conn, hash) diff --git a/spec/docker/container_spec.rb b/spec/docker/container_spec.rb index d5c8a34f..45175280 100644 --- a/spec/docker/container_spec.rb +++ b/spec/docker/container_spec.rb @@ -163,6 +163,32 @@ expect(subject.json["Name"]).to eq("/bob") end end + + context 'when a platform is given' do + let(:opts) { {"platform" => "linux/amd64"} } + + # /containers/create takes platform as a query parameter. A "platform" + # body key is accepted and silently ignored by the daemon, so sending it + # in the body means the requested platform has no effect at all. + it 'sends the platform as a query parameter' do + expect(Docker.connection).to receive(:post).with( + '/containers/create', + hash_including('platform' => 'linux/amd64'), + anything + ).and_return('{"Id":"deadbeef"}') + + subject + end + + it 'does not send the platform in the body' do + expect(Docker.connection).to receive(:post) { |_path, _query, opts| + expect(MultiJson.load(opts[:body])).to_not have_key('platform') + '{"Id":"deadbeef"}' + } + + subject + end + end end describe '#rename' do