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
36 changes: 36 additions & 0 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding"
"encoding/json"
"fmt"
"io"
"maps"
"os"
Expand Down Expand Up @@ -726,6 +727,7 @@ type Target struct {
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional" cty:"ulimits"`
Call *string `json:"call,omitempty" hcl:"call,optional" cty:"call"`
Entitlements []string `json:"entitlements,omitempty" hcl:"entitlements,optional" cty:"entitlements"`
ExtraHosts map[string]*string `json:"extra-hosts,omitempty" hcl:"extra-hosts,optional" cty:"extra-hosts"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a single host name can actually have multiple IPs assigned (typically, to support IPv4 and IPv6)
For this purpose, Compose's HostsList is a map[string][]string. Maybe simpler to keep using a plain []string to mimic --add-host build flag

Copy link
Member Author

@crazy-max crazy-max Jun 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah indeed this would not work with current logic. I guess with map[string]*string it would be smth like myhost = "8.8.8.8,::1" to have both v4/v6 ?

On LLB side I don't think we support that format though 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better align with LLB model then, to avoid yet another undocumented format that will require additional parsing.
IMHO []string is fine as an API, as this naturally matches with --add-host on the CLI

// IMPORTANT: if you add more fields here, do not forget to update newOverrides/AddOverrides and docs/bake-reference.md.

// linked is a private field to mark a target used as a linked one
Expand Down Expand Up @@ -764,6 +766,14 @@ func (t *Target) MarshalJSON() ([]byte, error) {
}
}

tgt.ExtraHosts = maps.Clone(t.ExtraHosts)
for k, v := range t.ExtraHosts {
if v != nil {
escaped := esc(*v)
tgt.ExtraHosts[k] = &escaped
}
}

return json.Marshal(tgt)
}

Expand Down Expand Up @@ -894,6 +904,15 @@ func (t *Target) Merge(t2 *Target) {
if t2.Entitlements != nil { // merge
t.Entitlements = append(t.Entitlements, t2.Entitlements...)
}
for k, v := range t2.ExtraHosts {
if v == nil {
continue
}
if t.ExtraHosts == nil {
t.ExtraHosts = map[string]*string{}
}
t.ExtraHosts[k] = v
}
t.Inherits = append(t.Inherits, t2.Inherits...)
}

Expand Down Expand Up @@ -1082,6 +1101,14 @@ func (t *Target) AddOverrides(overrides map[string]Override, ent *EntitlementCon
return errors.Errorf("invalid value %s for boolean key load", value)
}
t.Outputs = setLoadOverride(t.Outputs, load)
case "extra-hosts":
if len(keys) != 2 {
return errors.Errorf("invalid format for extra-hosts, expecting extra-hosts.<hostname>=<ip>")
}
if t.ExtraHosts == nil {
t.ExtraHosts = map[string]*string{}
}
t.ExtraHosts[keys[1]] = &value
default:
return errors.Errorf("unknown key: %s", keys[0])
}
Expand Down Expand Up @@ -1404,6 +1431,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
}
}

var extraHosts []string
for k, v := range t.ExtraHosts {
if v == nil {
continue
}
extraHosts = append(extraHosts, fmt.Sprintf("%s=%s", k, *v))
}

bo := &build.Options{
Inputs: bi,
Tags: t.Tags,
Expand All @@ -1415,6 +1450,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
NetworkMode: networkMode,
Linked: t.linked,
ShmSize: *shmSize,
ExtraHosts: extraHosts,
}

platforms, err := platformutil.Parse(t.Platforms)
Expand Down
4 changes: 4 additions & 0 deletions bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ target "webDEP" {
no-cache = true
shm-size = "128m"
ulimits = ["nofile=1024:1024"]
extra-hosts = {
my_hostname = "8.8.8.8"
}
}

target "webapp" {
Expand Down Expand Up @@ -64,6 +67,7 @@ target "webapp" {
require.Equal(t, true, *m["webapp"].NoCache)
require.Equal(t, "128m", *m["webapp"].ShmSize)
require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits)
require.Equal(t, map[string]*string{"my_hostname": ptrstr("8.8.8.8")}, m["webapp"].ExtraHosts)
require.Nil(t, m["webapp"].Pull)

require.Equal(t, 1, len(g))
Expand Down
11 changes: 11 additions & 0 deletions bake/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
}
}

extraHosts := map[string]*string{}
if s.Build.ExtraHosts != nil {
for k, v := range s.Build.ExtraHosts {
for _, ip := range v {
vv := ip
extraHosts[k] = &vv
}
}
}

var ssh []*buildflags.SSH
for _, bkey := range s.Build.SSH {
sshkey := composeToBuildkitSSH(bkey)
Expand Down Expand Up @@ -180,6 +190,7 @@ func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Conf
Secrets: secrets,
ShmSize: shmSize,
Ulimits: ulimits,
ExtraHosts: extraHosts,
}
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions bake/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ services:
- type=local,src=path/to/cache
cache_to:
- type=local,dest=path/to/cache
extra_hosts:
- "somehost:162.242.195.82"
- "myhostv6:::1"
ssh:
- key=/path/to/key
- default
Expand Down Expand Up @@ -76,6 +79,7 @@ secrets:
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
require.Equal(t, []string{"type=local,src=path/to/cache"}, stringify(c.Targets[1].CacheFrom))
require.Equal(t, []string{"type=local,dest=path/to/cache"}, stringify(c.Targets[1].CacheTo))
require.Equal(t, map[string]*string{"myhostv6": ptrstr("::1"), "somehost": ptrstr("162.242.195.82")}, c.Targets[1].ExtraHosts)
require.Equal(t, "none", *c.Targets[1].NetworkMode)
require.Equal(t, []string{"default", "key=/path/to/key"}, stringify(c.Targets[1].SSH))
require.Equal(t, []string{
Expand Down
17 changes: 17 additions & 0 deletions docs/bake-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ The following table shows the complete list of attributes that you can assign to
| [`description`](#targetdescription) | String | Description of a target |
| [`dockerfile-inline`](#targetdockerfile-inline) | String | Inline Dockerfile string |
| [`dockerfile`](#targetdockerfile) | String | Dockerfile location |
| [`entitlements`](#targetentitlements) | List | Permissions that the build process requires to run |
| [`extra-hosts`](#targetextra-hosts) | List | Customs host-to-IP mapping |
| [`inherits`](#targetinherits) | List | Inherit attributes from other targets |
| [`labels`](#targetlabels) | Map | Metadata for images |
| [`matrix`](#targetmatrix) | Map | Define a set of variables that forks a target into multiple targets. |
Expand Down Expand Up @@ -583,6 +585,20 @@ target "integration-tests" {

Entitlements are enabled with a two-step process. First, a target must declare the entitlements it requires. Secondly, when invoking the `bake` command, the user must grant the entitlements by passing the `--allow` flag or confirming the entitlements when prompted in an interactive terminal. This is to ensure that the user is aware of the possibly insecure permissions they are granting to the build process.

### `target.extra-hosts`

Use the `extra-hosts` attribute to define customs host-to-IP mapping for the
target. This has the same effect as passing a [`--add-host`][add-host] flag to
the build command.

```hcl
target "default" {
extra-hosts = {
my_hostname = "8.8.8.8"
}
}
```

### `target.inherits`

A target can inherit attributes from other targets.
Expand Down Expand Up @@ -1422,6 +1438,7 @@ target "webapp-dev" {

<!-- external links -->

[add-host]: https://docs.docker.com/reference/cli/docker/buildx/build/#add-host
[attestations]: https://docs.docker.com/build/attestations/
[bake_stdlib]: https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go
[build-arg]: https://docs.docker.com/reference/cli/docker/image/build/#build-arg
Expand Down
1 change: 1 addition & 0 deletions docs/reference/buildx_bake.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ You can override the following fields:
* `context`
* `dockerfile`
* `entitlements`
* `extra-hosts`
* `labels`
* `load`
* `no-cache`
Expand Down
26 changes: 26 additions & 0 deletions tests/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeCallMetadata,
testBakeMultiPlatform,
testBakeCheckCallOutput,
testBakeExtraHosts,
}

func testBakePrint(t *testing.T, sb integration.Sandbox) {
Expand Down Expand Up @@ -2162,6 +2163,31 @@ target "third" {
})
}

func testBakeExtraHosts(t *testing.T, sb integration.Sandbox) {
dockerfile := []byte(`
FROM busybox
RUN cat /etc/hosts | grep myhost | grep 1.2.3.4
`)
bakefile := []byte(`
target "default" {
extra-hosts = {
myhost = "1.2.3.4"
}
}
`)
dir := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)

out, err := bakeCmd(
sb,
withDir(dir),
)
require.NoError(t, err, out)
}

func writeTempPrivateKey(fp string) error {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
Expand Down