Skip to content

Commit

Permalink
ci: publish distroless images (#385)
Browse files Browse the repository at this point in the history
We've had a long history of Alpine vulnerability reports on Relay's
Docker images. The patches percolate into the upstream tools that are
bundled in Alpine, then into Alpine, then into the Alpine docker image
release, and finally into a Relay release.

Since Relay is a simple Go binary, we could take advantage of Google's
"distroless" container system. This is an extremely stripped down
debian12 image with "just" the Go binary. The attack surface is smaller
and we'd have less necessary maintenance.

This PR adds two new image manifests: 
- `static-debian12-nonroot` (based on
`gcr.io/distroless/static-debian12:nonroot`)
- `static-debian12-debug-nonroot` (based on
`gcr.io/distroless/static-debian12:debug-nonroot`).

Each manifest supports amd64, armv7, and armv8 architectures. There is
additional support for `ppc64le` and `s390x` in those base images, but I
haven't exposed them here yet in order to give a similar offering to the
Alpine image (note: we're missing `i386`.)
  • Loading branch information
cwaldren-ld authored Jun 10, 2024
1 parent 36cdd1b commit fe0155f
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 9 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ jobs:
run:
echo "value=$(jq -r '.version' < dist/metadata.json)" >> $GITHUB_OUTPUT
- uses: aquasecurity/trivy-action@master
id: scan-alpine
continue-on-error: true
with:
# Using an explicit tag rather than ld-relay:latest to ensure we're scanning the local image that we just built.
# It's not clear why, but it seems goreleaser doesn't create the :latest tag when skipping the publish step
Expand All @@ -72,3 +74,22 @@ jobs:
format: 'table'
exit-code: '1'
ignore-unfixed: true
- uses: aquasecurity/trivy-action@master
id: scan-distroless
continue-on-error: true
with:
image-ref: launchdarkly/ld-relay:${{ steps.image-tag.outputs.value }}-static-debian12-nonroot-amd64
format: 'table'
exit-code: '1'
ignore-unfixed: true
- uses: aquasecurity/trivy-action@master
continue-on-error: true
id: scan-debug-distroless
with:
image-ref: launchdarkly/ld-relay:${{ steps.image-tag.outputs.value }}-static-debian12-debug-nonroot-amd64
format: 'table'
exit-code: '1'
ignore-unfixed: true
- name: Fail if any of scan-alpine, scan-distroless, or scan-distroless-debug failed
if: ${{ steps.scan-alpine.outcome != 'success' || steps.scan-distroless.outcome != 'success' || steps.scan-debug-distroless.outcome != 'success' }}
run: exit 1
146 changes: 142 additions & 4 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ release:
# (we want Releaser to be responsible for doing all the GitHub release manipulations)

dockers:
# The following templates are for the Alpine-based image.
# i386
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-i386"
Expand Down Expand Up @@ -119,14 +120,107 @@ dockers:
build_flag_templates:
- "--pull"
- "--platform=linux/arm64/v8"

## The following image templates are for the nonroot debian12 distroless image.

# AMD64
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-nonroot-amd64"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot-amd64"
- "launchdarkly/ld-relay:latest-static-debian12-nonroot-amd64"
use: buildx
goos: linux
goarch: amd64
dockerfile: Dockerfile-static-debian12-nonroot.goreleaser
skip_push: false
build_flag_templates:
- "--pull"
- "--platform=linux/amd64"

# ARMv7
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-nonroot-armv7"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot-armv7"
- "launchdarkly/ld-relay:latest-static-debian12-nonroot-armv7"
use: buildx
goos: linux
goarch: arm
goarm: 7
dockerfile: Dockerfile-static-debian12-nonroot.goreleaser
skip_push: false
build_flag_templates:
- "--pull"
- "--platform=linux/arm/v7"

# ARM64v8
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-nonroot-arm64v8"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot-arm64v8"
- "launchdarkly/ld-relay:latest-static-debian12-nonroot-arm64v8"
use: buildx
goos: linux
goarch: arm64
dockerfile: Dockerfile-static-debian12-nonroot.goreleaser
skip_push: false
build_flag_templates:
- "--pull"
- "--platform=linux/arm64/v8"

## The following image templates are for the debug nonroot debian12 distroless image. This image is
## necessary to get a shell in the container for debugging purposes.

# AMD64
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-debug-nonroot-amd64"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot-amd64"
- "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot-amd64"
use: buildx
goos: linux
goarch: amd64
dockerfile: Dockerfile-static-debian12-debug-nonroot.goreleaser
skip_push: false
build_flag_templates:
- "--pull"
- "--platform=linux/amd64"

# ARMv7
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-debug-nonroot-armv7"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot-armv7"
- "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot-armv7"
use: buildx
goos: linux
goarch: arm
goarm: 7
dockerfile: Dockerfile-static-debian12-debug-nonroot.goreleaser
skip_push: false
build_flag_templates:
- "--pull"
- "--platform=linux/arm/v7"

# ARM64v8
- image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-debug-nonroot-arm64v8"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot-arm64v8"
- "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot-arm64v8"
use: buildx
goos: linux
goarch: arm64
dockerfile: Dockerfile-static-debian12-debug-nonroot.goreleaser
skip_push: false
build_flag_templates:
- "--pull"
- "--platform=linux/arm64/v8"

docker_manifests:
# For the Alpine image
- name_template: "launchdarkly/ld-relay:{{ .Version}}"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-amd64"
- "launchdarkly/ld-relay:{{ .Version }}-armv7"
- "launchdarkly/ld-relay:{{ .Version }}-arm64v8"
- "launchdarkly/ld-relay:{{ .Version }}-i386"
- "launchdarkly/ld-relay:{{ .Version }}-amd64"
- "launchdarkly/ld-relay:{{ .Version }}-armv7"
- "launchdarkly/ld-relay:{{ .Version }}-arm64v8"
- "launchdarkly/ld-relay:{{ .Version }}-i386"

- name_template: "launchdarkly/ld-relay:v{{ .Major }}"
skip_push: false
Expand All @@ -143,3 +237,47 @@ docker_manifests:
- "launchdarkly/ld-relay:latest-armv7"
- "launchdarkly/ld-relay:latest-arm64v8"
- "launchdarkly/ld-relay:latest-i386"

# For the static debian12 image
- name_template: "launchdarkly/ld-relay:latest-static-debian12-nonroot"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:latest-static-debian12-nonroot-amd64"
- "launchdarkly/ld-relay:latest-static-debian12-nonroot-armv7"
- "launchdarkly/ld-relay:latest-static-debian12-nonroot-arm64v8"

- name_template: "launchdarkly/ld-relay:{{ .Version}}-static-debian12-nonroot"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-nonroot-amd64"
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-nonroot-armv7"
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-nonroot-arm64v8"

- name_template: "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot-amd64"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot-armv7"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-nonroot-arm64v8"

# For the debug static debian12 image
- name_template: "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot-amd64"
- "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot-armv7"
- "launchdarkly/ld-relay:latest-static-debian12-debug-nonroot-arm64v8"

- name_template: "launchdarkly/ld-relay:{{ .Version}}-static-debian12-debug-nonroot"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-debug-nonroot-amd64"
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-debug-nonroot-armv7"
- "launchdarkly/ld-relay:{{ .Version }}-static-debian12-debug-nonroot-arm64v8"

- name_template: "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot"
skip_push: false
image_templates:
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot-amd64"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot-armv7"
- "launchdarkly/ld-relay:v{{ .Major }}-static-debian12-debug-nonroot-arm64v8"
10 changes: 10 additions & 0 deletions Dockerfile-static-debian12-debug-nonroot.goreleaser
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This is a Dockerfile used for release (published to dockerhub by goreleaser)

FROM gcr.io/distroless/static-debian12:debug-nonroot
# See "Runtime platform versions" in CONTRIBUTING.md

COPY ld-relay /usr/bin/ldr

EXPOSE 8030
ENV PORT=8030
ENTRYPOINT ["/usr/bin/ldr", "--config", "/ldr/ld-relay.conf", "--allow-missing-file", "--from-env"]
10 changes: 10 additions & 0 deletions Dockerfile-static-debian12-nonroot.goreleaser
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This is a Dockerfile used for release (published to dockerhub by goreleaser)

FROM gcr.io/distroless/static-debian12:nonroot
# See "Runtime platform versions" in CONTRIBUTING.md

COPY ld-relay /usr/bin/ldr

EXPOSE 8030
ENV PORT=8030
ENTRYPOINT ["/usr/bin/ldr", "--config", "/ldr/ld-relay.conf", "--allow-missing-file", "--from-env"]
2 changes: 1 addition & 1 deletion Dockerfile.goreleaser
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This is the Dockerfile used for release (published to dockerhub by goreleaser)
# This is a Dockerfile used for release (published to dockerhub by goreleaser)

FROM alpine:3.20.0
# See "Runtime platform versions" in CONTRIBUTING.md
Expand Down
54 changes: 50 additions & 4 deletions docs/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@

Using Docker is not required, but if you prefer using a Docker container we provide a Docker entrypoint to make this as easy as possible.

To build the `ld-relay` container:
```
We provide two distributions. The first is a based on Alpine Linux, while the second is based on Google's
["distroless"](https://github.com/GoogleContainerTools/distroless) debian12 images.


## Local Development

When developing locally, you can build the `ld-relay` Alpine container with the following command:
```shell
$ docker build -t ld-relay .
```

In Docker, the config file is expected to be found at `/ldr/ld-relay.conf`, unless you are using environment variables to configure the Relay Proxy. To learn more, read [Configuration](./configuration.md).
Please note that this Alpine [Dockerfile](../Dockerfile) is **not** the same one that is published to
[DockerHub](https://hub.docker.com/r/launchdarkly/ld-relay).

## Docker examples
It is a convenience for local development, whereas the Alpine image published to DockerHub is built during our release
process and is based on [Dockerfile.goreleaser](../Dockerfile.goreleaser).

In Docker, the config file is expected to be found at `/ldr/ld-relay.conf`, unless you are using environment variables
to configure the Relay Proxy. To learn more, read [Configuration](./configuration.md).

## Local Development Examples

To run a single environment, without Redis:
```shell
Expand All @@ -34,3 +47,36 @@ To run multiple environment, with Redis:
$ docker run --name redis redis:alpine
$ docker run --name ld-relay --link redis:redis -e USE_REDIS=1 -e LD_ENV_test="sdk-test-sdkKey" -e LD_PREFIX_test="ld:default:test" -e LD_ENV_prod="sdk-prod-sdkKey" -e LD_PREFIX_prod="ld:default:prod" ld-relay
```

## Production Deployment

In production, you may choose between our Distroless or Alpine Linux images. We recommend using the Distroless
images, as they present less of an attack surface, are smaller, and should require less continual patching.

Please note that the default Distroless image does not contain a debug shell.

### Distroless Variants

Relay's Distroless images are distributed in two variants. The first is intended for regular usage, while the
second is for debugging and contains a shell.

| Docker image tag suffix | Based on [Distroless](https://github.com/GoogleContainerTools/distroless) tag.. | Purpose |
|-----------------------------------|---------------------------------------------------------------------------------|--------------------------|
| `-static-debian12-nonroot` | `static-debian12:nonroot` | Normal usage |
| `-static-debian12-debug-nonroot-` | `static-debian12:debug-nonroot` | Contains a busybox shell |

To enter the busybox shell for debugging purposes on a running container (only available in the `-debug-nonroot`
variant):
```shell
docker exec -it [container name] /busybox/sh
```


### Supported Architectures

Alpine and Distroless are multi-arch images.

| Image | i386 | amd64 | armv7 | arm64v8 |
|------------|------|-------|-------|---------|
| Distroless |||||
| Alpine |||||

0 comments on commit fe0155f

Please sign in to comment.