Skip to content

Commit 047923d

Browse files
authored
image-mapper: map images in Dockerfiles (#257)
Add a command that maps image references in Dockerfiles to Chainguard. It attempts to be as low touch as possible, leaving the file formatted exactly as it is and just replacing the images where it finds them. As part of this I've refactored the tag matching logic because I wanted to only map to -dev tags, unless the image has no -dev tags at all. This wasn't possible with the abstraction I was previously using.
1 parent 2b126cf commit 047923d

25 files changed

+975
-189
lines changed

image-mapper/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,32 @@ important, where possible, to use tags that are being actively maintained.
5252

5353
Refer to [this page](./docs/map.md) for more details.
5454

55+
### Dockerfile
56+
57+
The `dockerfile` subcommand maps image references in a Dockerfile to Chainguard.
58+
59+
```
60+
$ cat Dockerfile
61+
FROM python:3.13
62+
63+
WORKDIR /app
64+
65+
COPY run.py run.py
66+
67+
ENTRYPOINT ["python", "/app/run.py"]
68+
69+
$ ./image-mapper map dockerfile Dockerfile
70+
FROM cgr.dev/chainguard/python:3.13-dev
71+
72+
WORKDIR /app
73+
74+
COPY run.py run.py
75+
76+
ENTRYPOINT ["python", "/app/run.py"]
77+
```
78+
79+
Refer to [this page](./docs/map_dockerfile.md) for more details.
80+
5581
### Helm
5682

5783
The `helm-chart` and `helm-values` subcommands extract image related values and

image-mapper/cmd/map.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ func MapCommand() *cobra.Command {
6363
rootCmd.Flags().StringVar(&opts.Repo, "repository", "cgr.dev/chainguard", "Modifies the repository URI in the mappings. For instance, registry.internal.dev/chainguard would result in registry.internal.dev/chainguard/<image> in the output.")
6464

6565
cmd.AddCommand(
66+
MapDockerfileCommand(),
6667
MapHelmChartCommand(),
6768
MapHelmValuesCommand(),
6869
)

image-mapper/cmd/map_dockerfile.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"github.com/chainguard-dev/customer-success/scripts/image-mapper/internal/dockerfile"
9+
"github.com/chainguard-dev/customer-success/scripts/image-mapper/internal/mapper"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func MapDockerfileCommand() *cobra.Command {
14+
opts := struct {
15+
Repo string
16+
}{}
17+
cmd := &cobra.Command{
18+
Use: "dockerfile",
19+
Short: "Map image references in a Dockerfile to their Chainguard equivalents.",
20+
Example: `
21+
# Map a Dockerfile
22+
image-mapper map dockerfile Dockerfile
23+
24+
# Map a Dockerfile from stdin
25+
cat Dockerfile | image-mapper map dockerfile -
26+
27+
# Override the repository in the mappings with your own mirror or proxy. For instance, cgr.dev/chainguard/<image> would become registry.internal/cgr/<image> in the output.
28+
image-mapper map dockerfile Dockerfile --repository=registry.internal/cgr
29+
`,
30+
Args: cobra.MinimumNArgs(1),
31+
RunE: func(cmd *cobra.Command, args []string) error {
32+
var (
33+
input []byte
34+
err error
35+
)
36+
switch args[0] {
37+
case "-":
38+
input, err = io.ReadAll(os.Stdin)
39+
if err != nil {
40+
return fmt.Errorf("reading stdin: %w", err)
41+
}
42+
default:
43+
input, err = os.ReadFile(args[0])
44+
if err != nil {
45+
return fmt.Errorf("reading file: %s: %w", args[0], err)
46+
}
47+
}
48+
49+
output, err := dockerfile.Map(cmd.Context(), input, mapper.WithRepository(opts.Repo))
50+
if err != nil {
51+
return fmt.Errorf("mapping dockerfile: %w", err)
52+
}
53+
54+
if _, err := os.Stdout.Write(output); err != nil {
55+
return fmt.Errorf("writing output: %w", err)
56+
}
57+
58+
return nil
59+
},
60+
}
61+
62+
cmd.Flags().StringVar(&opts.Repo, "repository", "cgr.dev/chainguard", "Modifies the repository URI in the mappings. For instance, registry.internal.dev/chainguard would result in registry.internal.dev/chainguard/<image> in the output.")
63+
64+
return cmd
65+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Map Dockerfile
2+
3+
Map images references in a Dockerfile to Chainguard images.
4+
5+
## How It Works
6+
7+
The `dockerfile` subcommand maps any image references it finds in `FROM <image>`,
8+
`COPY --from=<image>` or `RUN --mount-type=bind,from=<image>` directives to
9+
Chainguard.
10+
11+
It will map images to `-dev` tags because they are more likely to work out of
12+
the box as drop in replacements.
13+
14+
## Basic Usage
15+
16+
Given a `Dockerfile` like this:
17+
18+
```
19+
FROM python:3.13 AS python
20+
21+
WORKDIR /app
22+
23+
COPY run.py run.py
24+
25+
ENTRYPOINT ["python", "/app/run.py"]
26+
```
27+
28+
Use the `dockerfile` subcommand to map it to Chainguard images. It returns the
29+
result to stdout.
30+
31+
```
32+
$ ./image-mapper map dockerfile Dockerfile
33+
FROM cgr.dev/chainguard/python:3.13-dev
34+
35+
WORKDIR /app
36+
37+
COPY run.py run.py
38+
39+
ENTRYPOINT ["python", "/app/run.py"]
40+
```
41+
42+
You can also provide the Dockerfile via stdin:
43+
44+
```
45+
$ cat Dockerfile | ./image-mapper map dockerfile -
46+
```
47+
48+
## Repository Prefix
49+
50+
Use the `--repository` flag to replace `cgr.dev/chainguard` with a custom
51+
repository.
52+
53+
```
54+
$ ./image-mapper map dockerfile Dockerfile --repository=registry.internal/cgr
55+
FROM registry.internal/cgr/python:3.13-dev
56+
57+
WORKDIR /app
58+
59+
COPY run.py run.py
60+
61+
ENTRYPOINT ["python", "/app/run.py"]
62+
```
63+
64+
## Known Limitations
65+
66+
There are a few rough edges that haven't been smoothed out yet.
67+
68+
### Args
69+
70+
The mapper supports resolving arguments to figure out which images they refer
71+
to but it isn't clever enough to go back and update those arguments.
72+
73+
For instance, a file like this:
74+
75+
```
76+
ARG REGISTRY=docker.io
77+
ARG IMAGE=library/python
78+
ARG TAG=3.13
79+
FROM ${REGISTRY}/${IMAGE}:${TAG}
80+
```
81+
82+
Would become:
83+
84+
```
85+
ARG REGISTRY=docker.io
86+
ARG IMAGE=library/python
87+
ARG TAG=3.13
88+
FROM cgr.dev/chainguard/python:3.13-dev
89+
```
90+
91+
### Multi Line Directives
92+
93+
If it updates an image reference in a multi line directive then it will squash
94+
the directive into one line.
95+
96+
For instance, lines like this:
97+
98+
```
99+
RUN --mount=type=bind,from=ubuntu,target=/bin/cat \
100+
cat run.py
101+
```
102+
103+
Would become:
104+
105+
```
106+
RUN --mount=type=bind,from=cgr.dev/chainguard/chainguard-base:latest,target=/bin/cat cat run.py
107+
```

image-mapper/go.mod

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.24.5
55
require (
66
github.com/google/go-cmp v0.7.0
77
github.com/google/go-containerregistry v0.20.6
8+
github.com/moby/buildkit v0.26.3
89
github.com/spf13/cobra v1.10.1
910
gopkg.in/yaml.v3 v3.0.1
1011
helm.sh/helm/v3 v3.19.4
@@ -25,13 +26,14 @@ require (
2526
github.com/containerd/containerd v1.7.29 // indirect
2627
github.com/containerd/errdefs v1.0.0 // indirect
2728
github.com/containerd/log v0.1.0 // indirect
28-
github.com/containerd/platforms v0.2.1 // indirect
29+
github.com/containerd/platforms v1.0.0-rc.2 // indirect
30+
github.com/containerd/typeurl/v2 v2.2.3 // indirect
2931
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
3032
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
3133
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
3234
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
3335
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
34-
github.com/fatih/color v1.13.0 // indirect
36+
github.com/fatih/color v1.18.0 // indirect
3537
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
3638
github.com/go-errors/errors v1.4.2 // indirect
3739
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
@@ -54,14 +56,14 @@ require (
5456
github.com/jmoiron/sqlx v1.4.0 // indirect
5557
github.com/josharian/intern v1.0.0 // indirect
5658
github.com/json-iterator/go v1.1.12 // indirect
57-
github.com/klauspost/compress v1.18.0 // indirect
59+
github.com/klauspost/compress v1.18.1 // indirect
5860
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
5961
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
6062
github.com/lib/pq v1.10.9 // indirect
6163
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
6264
github.com/mailru/easyjson v0.7.7 // indirect
63-
github.com/mattn/go-colorable v0.1.13 // indirect
64-
github.com/mattn/go-isatty v0.0.17 // indirect
65+
github.com/mattn/go-colorable v0.1.14 // indirect
66+
github.com/mattn/go-isatty v0.0.20 // indirect
6567
github.com/mattn/go-runewidth v0.0.9 // indirect
6668
github.com/mitchellh/copystructure v1.2.0 // indirect
6769
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@@ -77,6 +79,7 @@ require (
7779
github.com/opencontainers/image-spec v1.1.1 // indirect
7880
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
7981
github.com/pkg/errors v0.9.1 // indirect
82+
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
8083
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
8184
github.com/rubenv/sql-migrate v1.8.0 // indirect
8285
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@@ -96,10 +99,10 @@ require (
9699
golang.org/x/sys v0.38.0 // indirect
97100
golang.org/x/term v0.37.0 // indirect
98101
golang.org/x/text v0.31.0 // indirect
99-
golang.org/x/time v0.12.0 // indirect
100-
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
101-
google.golang.org/grpc v1.72.1 // indirect
102-
google.golang.org/protobuf v1.36.5 // indirect
102+
golang.org/x/time v0.14.0 // indirect
103+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
104+
google.golang.org/grpc v1.76.0 // indirect
105+
google.golang.org/protobuf v1.36.10 // indirect
103106
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
104107
gopkg.in/inf.v0 v0.9.1 // indirect
105108
k8s.io/api v0.34.2 // indirect

0 commit comments

Comments
 (0)