From 7f68f4991501e0e315c06248323b5b9b27d722b5 Mon Sep 17 00:00:00 2001 From: Bryan Latten Date: Tue, 14 Dec 2021 22:36:34 -0500 Subject: [PATCH] Dockerfile: updated bases to 4.0 (#78) * Dockerfile: updated bases to 4.0 * Github actions: replace Hub build with multi-arch * Traivs: multi-arch, GH action CI * CI: separated out publish from PR action * CI: localhost port publishing networking * CI: working action and documentation * CI: work around mounted docker sockets * Review: tweaks * Dockerfile: consolidated woff fixes, ubuntu + cent * Dockerfile-centos: upgrading nginx, TLS 1.3 support * README: X64 warning * Woff: newline for @bossjones Co-authored-by: Bryan Latten --- .github/workflows/ci.yml | 101 ++++++++++++++++++++ .github/workflows/publish.yml | 67 +++++++++++++ .gitignore | 3 + .travis.yml | 4 + Dockerfile | 10 +- Dockerfile-alpine | 2 +- Dockerfile-centos | 18 +++- README.md | 29 ++++++ container/root/scripts/fix_woff_support.sh | 14 +++ container/root/tests/common/nginx.goss.yaml | 1 + 10 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 container/root/scripts/fix_woff_support.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0e68ac3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,101 @@ +name: ci + +on: + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + props: + - Dockerfile: Dockerfile + - Dockerfile: Dockerfile-alpine + - Dockerfile: Dockerfile-centos + platform: + - linux/amd64 + - linux/arm64 + env: + TEST_MATCH: Welcome to nginx! + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Detect host configuration + run: | + # NOTE: Docker host configuration determines the networking target for integration testing + v=$(mount | grep "/run/docker.sock") + TARGET_HOST= + + if [ -n "$v" ]; then + echo "Injected docker socket detected" + TARGET_HOST="host.docker.internal" + elif [ -S /var/run/docker.sock ]; then + TARGET_HOST="localhost" + else + echo "No Docker socket detected, fail" + exit 1 + fi + echo "TARGET_HOST=${TARGET_HOST}" >> $GITHUB_ENV + - + # Build and execute in multiple configurations: vanilla, with env overrides, with TLS enabled + name: Build and test + run: | + # NOTE: docker qemu and buildx setup actions create a black hole for build cache layers, avoid unless pushing externally + # Setup multi-arch platforms, noop if already installed for builder + docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 + + TARGET_PLATFORM=${{ matrix.platform }} + TARGET_DOCKERFILE=${{ matrix.props.Dockerfile }} + + # Since containers may or may not be against the same docker engine, create a matrix-unique tag name for outputs + TAG_NAME="docker-nginx-${TARGET_DOCKERFILE}-${TARGET_PLATFORM}" + # Formats as lowercase + TAG_NAME=$(echo $TAG_NAME | tr '[:upper:]' '[:lower:]') + # Removes slashes + TAG_NAME=$(echo $TAG_NAME | sed 's/\///') + + echo $TAG_NAME + + docker buildx build --platform $TARGET_PLATFORM --iidfile $TAG_NAME -t $TAG_NAME -f $TARGET_DOCKERFILE . + + # NOTE: multi-arch builds may not be accessible by docker tag, instead target by ID + BUILD_SHA=$(cat ./$TAG_NAME) + + # Remove sha256: from tag identifier + BUILD_SHA=$(echo $BUILD_SHA | sed 's/sha256\://') + + # Generate self-signed certificates + mkdir -p certs + openssl genrsa -out ./certs/ca.key 2048 + openssl req -new -key ./certs/ca.key -out ./certs/ca.csr -subj '/CN=localhost' + openssl x509 -req -days 365 -in ./certs/ca.csr -signkey ./certs/ca.key -out ./certs/ca.crt + + # Run various configurations of containers + CONTAINER_VANILLA=$(docker run --platform $TARGET_PLATFORM --rm -p 8080 -d $BUILD_SHA) + CONTAINER_ENV_FILE=$(docker run --platform $TARGET_PLATFORM --rm -p 8080 -d --env-file ./.test.env $BUILD_SHA) + CONTAINER_HTTPS=$(docker run --platform $TARGET_PLATFORM --rm -p 8080 -d -e SERVER_ENABLE_HTTPS=true -v $(pwd)/certs:/etc/nginx/certs:ro $BUILD_SHA) + + # Retrieve dynamically-allocated host port + VANILLA_PORT=$(docker inspect --format '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' $CONTAINER_VANILLA) + ENV_FILE_PORT=$(docker inspect --format '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' $CONTAINER_ENV_FILE) + HTTPS_PORT=$(docker inspect --format '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' $CONTAINER_HTTPS) + + # Wait for containers to boot (in background) + sleep 5 + + TARGET_HOST=${{ env.TARGET_HOST }} + echo "HOSTING ${TARGET_HOST}" + + # Check for nginx test page response + curl ${TARGET_HOST}:${VANILLA_PORT} | grep "${{ env.TEST_MATCH }}" + curl ${TARGET_HOST}:${ENV_FILE_PORT} | grep "${{ env.TEST_MATCH }}" + curl -k https://${TARGET_HOST}:${HTTPS_PORT} | grep "${{ env.TEST_MATCH }}" + + # Cleanup + docker kill $CONTAINER_VANILLA + docker kill $CONTAINER_ENV_FILE + docker kill $CONTAINER_HTTPS + docker rmi $BUILD_SHA diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..39a4664 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,67 @@ +name: publish + +on: + push: + tags: + - '*' + +jobs: + publish: + runs-on: ubuntu-latest + env: + IMAGE_BASE: behance/docker-nginx + strategy: + matrix: + props: + # This is the default variant-less distribution (ex. 3.2.1) + - Dockerfile: Dockerfile + # Variant distributions below all have semantic versions + suffix (ex. 3.2.1-alpine) + - Dockerfile: Dockerfile-alpine + suffix: alpine + - Dockerfile: Dockerfile-centos + suffix: centos + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Add tag suffix + if: matrix.props.suffix + run: | + echo TAG_SUFFIX="-${{ matrix.props.suffix }}" >> $GITHUB_ENV + - + name: Docker meta + id: meta + if: github.event_name != 'pull_request' + uses: docker/metadata-action@v3 + with: + images: ${{ env.IMAGE_BASE }} + tags: | + type=semver,pattern={{major}}.{{minor}}.{{patch}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + flavor: | + latest=auto + suffix=${{ env.TAG_SUFFIX }} + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build + push + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64,linux/arm64 + file: ${{ matrix.props.Dockerfile }} + tags: ${{ steps.meta.outputs.tags }} + push: ${{ github.event_name != 'pull_request' }} diff --git a/.gitignore b/.gitignore index dd059c3..f2ea379 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ # SASS .sass-cache *.css.map + +# Certificates +certs/ diff --git a/.travis.yml b/.travis.yml index 9772818..6d8e75c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,10 @@ env: services: - docker +arch: + - amd64 + - arm64 + script: - docker build -t nginxtest -f ${DOCKERFILE} . - mkdir certs diff --git a/Dockerfile b/Dockerfile index 62079e4..c846da7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM behance/docker-base:3.0-ubuntu-20.04 +FROM behance/docker-base:4.0-ubuntu-20.04 # Use in multi-phase builds, when an init process requests for the container to gracefully exit, so that it may be committed # Used with alternative CMD (worker.sh), leverages supervisor to maintain long-running processes @@ -43,15 +43,11 @@ COPY --chown=www-data ./container/root / # Set nginx to listen on defined port # NOTE: order of operations is important, new config had to already installed from repo (above) # - Make temp directory for .nginx runtime files -# - Remove older WOFF mime-type -# - Add again with newer mime-type -# - Also add mime-type for WOFF2 +# - Fix woff mime type support # Set permissions to allow image to be run under a non root user RUN sed -i "s/listen [0-9]*;/listen ${CONTAINER_PORT};/" $CONF_NGINX_SITE && \ mkdir /tmp/.nginx && \ - sed -i "/application\/font-woff/d" /etc/nginx/mime.types && \ - sed -i "s/}/\n font\/woff woff;&/" /etc/nginx/mime.types && \ - sed -i "s/}/\n font\/woff2 woff2;\n&/g" /etc/nginx/mime.types && \ + /bin/bash -e /scripts/fix_woff_support.sh && \ /bin/bash -e /scripts/set_permissions.sh RUN goss -g /tests/ubuntu/nginx.goss.yaml validate && \ diff --git a/Dockerfile-alpine b/Dockerfile-alpine index ded266c..c81d50a 100644 --- a/Dockerfile-alpine +++ b/Dockerfile-alpine @@ -1,4 +1,4 @@ -FROM behance/docker-base:3.0-alpine +FROM behance/docker-base:4.0-alpine # Use in multi-phase builds, when an init process requests for the container to gracefully exit, so that it may be committed # Used with alternative CMD (worker.sh), leverages supervisor to maintain long-running processes diff --git a/Dockerfile-centos b/Dockerfile-centos index bff1d83..6b5c169 100644 --- a/Dockerfile-centos +++ b/Dockerfile-centos @@ -1,4 +1,4 @@ -FROM behance/docker-base:3.0-centos-7 +FROM behance/docker-base:4.0-centos-7 # Use in multi-phase builds, when an init process requests for the container to gracefully exit, so that it may be committed # Used with alternative CMD (worker.sh), leverages supervisor to maintain long-running processes @@ -12,11 +12,18 @@ ENV CONTAINER_ROLE=web \ # Using a non-privileged port to prevent having to use setcap internally EXPOSE ${CONTAINER_PORT} -# - Update security packages, only +# - Update security packages +# - Install new stable version of nginx RUN /bin/bash -e /security_updates.sh && \ - yum -y -q install epel-release && \ + mkdir -p /etc/yum.repos.d && \ + echo $'[nginx-stable] \n\ +name=nginx stable repo \n\ +baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ \n\ +gpgcheck=1 \n\ +enabled=1 \n\ +name=nginx stable repo \n\ +gpgkey=https://nginx.org/keys/nginx_signing.key' > /etc/yum.repos.d/nginx.repo && \ yum -y -q install nginx ca-certificates && \ - yum -y -q remove epel-release && \ /bin/bash -e /clean.sh # Overlay the root filesystem from this repo @@ -25,12 +32,15 @@ COPY --chown=nginx ./container/root / # - Set nginx to listen on defined port # - NOTE: order of operations is important, new config had to already installed from repo (above) # - Make temp directory for .nginx runtime files +# - Fix woff mime type support # - Update nginx.conf user # - Set permissions to allow image to be run under a non root user RUN sed -i "s/listen [0-9]*;/listen ${CONTAINER_PORT};/" $CONF_NGINX_SITE && \ mkdir /tmp/.nginx && \ + /bin/bash -e /scripts/fix_woff_support.sh && \ sed -i "s/^user .*$/user ${NOT_ROOT_USER};/" ${CONF_NGINX_SERVER} && \ /bin/bash -e /scripts/set_permissions.sh +RUN yum update -y -q nginx RUN goss -g /tests/centos/nginx.goss.yaml validate && \ /aufs_hack.sh diff --git a/README.md b/README.md index 6e4e374..9a6b62c 100644 --- a/README.md +++ b/README.md @@ -105,3 +105,32 @@ container's use is in configuration and process. The `./container/root` repo dir to the folders in there will be present in the final image. Nginx is currently set up as an S6 service in `/etc/services-available/nginx`, during default environment conditions, it will symlink itself to be supervised under `/etc/services.d/nginx`. When running under worker entrypoint (`worker.sh`), it will not be S6's `service.d` folder to be supervised. + + +### Release Management + +Github actions provide the machinery for testing (ci.yaml) and producing tags distributed through Docker Hub (publish.yaml). Testing will confirm that `nginx` is able to serve content in various configurations, but also that it can terminate TLS with self-signed certificates. Once a tested and approved PR is merged, simply cutting a new semantically-versioned tag will generate the following matrix of tagged builds: +- `[major].[minor].[patch](?-variant)` +- `[major].[minor](?-variant)` +- `[major](?-variant)` +Platform support is available for architectures: +- `linux/arm64` +- `linux/amd64` + +To add new variant based on a new Dockerfile, add an entry to `matrix.props` within `./github/workflows` YAML files. + +### Github Actions: Simulation + +docker-nginx uses Github Actions for CI/CD. Simulated workflows can be achieved locally with `act`. All commands must be executes from repository root. + +Pre-reqs: tested on Mac +1. [Docker Desktop](https://www.docker.com/products/docker-desktop) +1. [act](https://github.com/nektos/act) + +Pull request simulation: executes successfully, but only on ARM devices (ex. Apple M1). ARM emulation through QEMU on X64 machines does not implement the full kernel functionality required by nginx at this time. +- `act pull_request` + +Publish simulation: executes, but fails (intentionally) without credentials +- `act` + + diff --git a/container/root/scripts/fix_woff_support.sh b/container/root/scripts/fix_woff_support.sh new file mode 100644 index 0000000..b680b95 --- /dev/null +++ b/container/root/scripts/fix_woff_support.sh @@ -0,0 +1,14 @@ +#!/bin/bash -e + +# Removes legacy woff type +sed -i "/application\/font-woff/d" /etc/nginx/mime.types + +# Detects if woff support is already present +if grep -Fxq "font/woff" /etc/nginx/mime.types +then + echo "Woff type detected, no changes necessary" +else + echo "Woff type not detected, adding..." + sed -i "s/}/\n font\/woff woff;&/" /etc/nginx/mime.types + sed -i "s/}/\n font\/woff2 woff2;\n&/g" /etc/nginx/mime.types +fi diff --git a/container/root/tests/common/nginx.goss.yaml b/container/root/tests/common/nginx.goss.yaml index 8d96925..7dcd492 100644 --- a/container/root/tests/common/nginx.goss.yaml +++ b/container/root/tests/common/nginx.goss.yaml @@ -53,6 +53,7 @@ file: /etc/nginx/mime.types: exists: true contains: + - '/#{0}text\/html/' # assert that file has basic content - '/#{0}font\/woff\s*woff;/' - '/#{0}font\/woff2\s*woff2;/' - '!/application\/font-woff/'