diff --git a/.dockerignore b/.dockerignore index ead0e71..f4647c6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,7 @@ maintain/ LICENSE *.md img/ -node_modules/ \ No newline at end of file +node_modules/ + +# custom +.env \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 56bbd5d..471fe64 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ +# default * text=auto *.sh eol=lf \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fd9204f..f43634c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -10,6 +10,12 @@ on: required: false default: 'docker' + runs-on: + description: 'set runs-on for workflow (github or selfhosted)' + type: string + required: false + default: 'ubuntu-22.04' + release: description: 'set WORKFLOW_GITHUB_RELEASE' required: false @@ -19,30 +25,15 @@ on: description: 'set WORKFLOW_GITHUB_README' required: false default: 'false' - - image: - description: 'set IMAGE' - required: false - - uid: - description: 'set IMAGE_UID' - required: false - - gid: - description: 'set IMAGE_GID' - required: false - - semverprefix: - description: 'prefix for semver tags' - required: false - - semversuffix: - description: 'suffix for semver tags' + + etc: + description: 'base64 encoded json string' required: false jobs: docker: - runs-on: ubuntu-22.04 + runs-on: ${{ inputs.runs-on }} + timeout-minutes: 1440 services: registry: @@ -69,12 +60,17 @@ jobs: script: | const { existsSync, readFileSync } = require('node:fs'); const { resolve } = require('node:path'); + const { inspect } = require('node:util'); + const { Buffer } = require('node:buffer'); const inputs = `${{ toJSON(github.event.inputs) }}`; const opt = {input:{}, dot:{}}; try{ if(inputs.length > 0){ opt.input = JSON.parse(inputs); + if(opt.input?.etc){ + opt.input.etc = JSON.parse(Buffer.from(opt.input.etc, 'base64').toString('ascii')); + } } }catch(e){ core.warning('could not parse github.event.inputs'); @@ -95,27 +91,30 @@ jobs: core.setFailed(e); } + core.info(inspect(opt, {showHidden:false, depth:null, colors:true})); + const docker = { image:{ - name:(opt.input?.image || opt.dot.image), + name:opt.dot.image, arch:(opt.dot.arch || 'linux/amd64,linux/arm64'), - prefix:((opt.input?.semverprefix) ? `${opt.input?.semverprefix}-` : ''), - suffix:((opt.input?.semversuffix) ? `-${opt.input?.semversuffix}` : ''), + prefix:((opt.input?.etc?.semverprefix) ? `${opt.input?.etc?.semverprefix}-` : ''), + suffix:((opt.input?.etc?.semversuffix) ? `-${opt.input?.etc?.semversuffix}` : ''), description:(opt.dot?.readme?.description || ''), tags:[], }, app:{ image:opt.dot.image, name:opt.dot.name, - version:opt.dot.semver.version, + version:(opt.input?.etc?.version || opt.dot.semver.version), root:opt.dot.root, - UID:(opt.input?.uid || 1000), - GID:(opt.input?.gid || 1000), + UID:(opt.input?.etc?.uid || 1000), + GID:(opt.input?.etc?.gid || 1000), no_cache:new Date().getTime(), }, cache:{ registry:'localhost:5000/', - } + }, + tags:[], }; docker.cache.name = `${docker.image.name}:${docker.image.prefix}buildcache${docker.image.suffix}`; @@ -124,21 +123,37 @@ jobs: docker.app.suffix = docker.image.suffix; // setup tags - const semver = opt.dot.semver.version.split('.'); - docker.image.tags.push(`${context.sha.substring(0,7)}`); - if(Array.isArray(semver)){ - if(semver.length >= 1) docker.image.tags.push(`${semver[0]}`); - if(semver.length >= 2) docker.image.tags.push(`${semver[0]}.${semver[1]}`); - if(semver.length >= 3) docker.image.tags.push(`${semver[0]}.${semver[1]}.${semver[2]}`); + if(opt.input?.etc?.dockerfile !== 'arch.dockerfile' && opt.input?.etc?.tag){ + docker.image.tags.push(`${context.sha.substring(0,7)}`); + docker.image.tags.push(opt.input.etc.tag); + docker.image.tags.push(`${opt.input.etc.tag}-${docker.app.version}`); + docker.cache.name = `${docker.image.name}:buildcache-${opt.input.etc.tag}`; + }else if(opt.dot?.semver?.version){ + const semver = opt.dot.semver.version.split('.'); + docker.image.tags.push(`${context.sha.substring(0,7)}`); + if(Array.isArray(semver)){ + if(semver.length >= 1) docker.image.tags.push(`${semver[0]}`); + if(semver.length >= 2) docker.image.tags.push(`${semver[0]}.${semver[1]}`); + if(semver.length >= 3) docker.image.tags.push(`${semver[0]}.${semver[1]}.${semver[2]}`); + } + if(opt.dot.semver?.stable && new RegExp(opt.dot.semver.stable, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('stable'); + if(opt.dot.semver?.latest && new RegExp(opt.dot.semver.latest, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('latest'); + }else if(opt.input?.etc?.version && opt.input.etc.version === 'latest'){ + docker.image.tags.push('latest'); } - if(opt.dot.semver?.stable && new RegExp(opt.dot.semver.stable, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('stable'); - if(opt.dot.semver?.latest && new RegExp(opt.dot.semver.latest, 'ig').test(docker.image.tags.join(','))) docker.image.tags.push('latest'); - for(let i=0; i{ + try{ + const etc = { + build:{ + args:{ + NGINX_CONFIGURATION:'full', + } + }, + semverprefix:'full', + }; + core.exportVariable('WORKFLOW_BASE64JSON', Buffer.from(JSON.stringify(etc)).toString('base64')); + }catch(e){ + core.setFailed(`workflow failed: ${e}`); + } + })(); + + - name: build docker image + uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7 + with: + workflow: docker.yml + token: "${{ secrets.REPOSITORY_TOKEN }}" + inputs: '{ "release":"false", "readme":"true", "etc":"${{ env.WORKFLOW_BASE64JSON }}" }' \ No newline at end of file diff --git a/.github/workflows/tags.yml b/.github/workflows/tags.yml index 76e3500..cd6d4de 100644 --- a/.github/workflows/tags.yml +++ b/.github/workflows/tags.yml @@ -4,22 +4,32 @@ on: tags: - 'v*' jobs: - docker: + node: runs-on: ubuntu-latest steps: + - name: init / base64 nested json + uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298 + with: + script: | + const { Buffer } = require('node:buffer'); + (async()=>{ + try{ + const etc = { + build:{ + args:{ + NGINX_CONFIGURATION:'light', + } + } + }; + core.exportVariable('WORKFLOW_BASE64JSON', Buffer.from(JSON.stringify(etc)).toString('base64')); + }catch(e){ + core.setFailed(`workflow failed: ${e}`); + } + })(); + - name: build docker image uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7 with: workflow: docker.yml token: "${{ secrets.REPOSITORY_TOKEN }}" - inputs: '{ "release":"true", "readme":"true" }' - - docker-unraid: - runs-on: ubuntu-latest - steps: - - name: build docker image for unraid community - uses: the-actions-org/workflow-dispatch@3133c5d135c7dbe4be4f9793872b6ef331b53bc7 - with: - workflow: docker.yml - token: "${{ secrets.REPOSITORY_TOKEN }}" - inputs: '{ "release":"false", "readme":"false", "uid":"99", "gid":"100", "semversuffix":"unraid", "run-name":"docker-unraid" }' \ No newline at end of file + inputs: '{ "release":"true", "readme":"true", "etc":"${{ env.WORKFLOW_BASE64JSON }}" }' \ No newline at end of file diff --git a/.gitignore b/.gitignore index b411486..7bfecea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # default maintain/ -node_modules/ \ No newline at end of file +node_modules/ + +# custom +.env \ No newline at end of file diff --git a/.json b/.json index dc586df..3e33863 100644 --- a/.json +++ b/.json @@ -10,12 +10,15 @@ }, "readme":{ - "description":"Nginx with additional plugins and custom compiled", - "parent":{ - "image":"11notes/alpine:stable" - }, + "description":"Nginx, slim and distroless to be used behind a reverse proxy or as full version", "built":{ "nginx":"https://nginx.org" + }, + "distroless":{ + "layers":[ + "11notes/distroless", + "11notes/distroless:curl" + ] } } } \ No newline at end of file diff --git a/arch.dockerfile b/arch.dockerfile index 4bc46ea..d8648ad 100644 --- a/arch.dockerfile +++ b/arch.dockerfile @@ -1,100 +1,297 @@ +ARG APP_UID=1000 +ARG APP_GID=1000 + # :: Util FROM 11notes/util AS util # :: Build / nginx - FROM alpine/git AS build - ARG APP_VERSION + FROM alpine AS build + ARG TARGETARCH + ARG TARGETPLATFORM + ARG TARGETVARIANT ARG APP_ROOT - ENV MODULE_HEADERS_MORE_NGINX_VERSION=0.37 + ARG APP_VERSION + ARG APP_NGINX_CONFIGURATION + ENV BUILD_ROOT=/nginx-${APP_VERSION} + ENV BUILD_BIN=${BUILD_ROOT}/objs/nginx + ENV NGINX_PREFIX=/etc/nginx + ENV BUILD_DEPENDENCY_OPENSSL_VERSION=3.5.0 + ENV BUILD_DEPENDENCY_OPENSSL_ROOT=/openssl-${BUILD_DEPENDENCY_OPENSSL_VERSION} + ENV BUILD_DEPENDENCY_ZLIB_VERSION=1.3.1 + ENV BUILD_DEPENDENCY_ZLIB_ROOT=/zlib-${BUILD_DEPENDENCY_ZLIB_VERSION} + ENV BUILD_DEPENDENCY_PCRE2_VERSION=10.45 + ENV BUILD_DEPENDENCY_PCRE2_ROOT=/pcre2-${BUILD_DEPENDENCY_PCRE2_VERSION} + ENV BUILD_DEPENDENCY_HEADERS_MORE_VERSION=0.38 + ENV BUILD_DEPENDENCY_HEADERS_MORE_ROOT=/headers-more-nginx-module-${BUILD_DEPENDENCY_HEADERS_MORE_VERSION} + ENV BUILD_DEPENDENCY_BROTLI_ROOT=/ngx_brotli + ENV BUILD_DEPENDENCY_NJS_VERSION=0.8.10 + ENV BUILD_DEPENDENCY_NJS_ROOT=/njs-${BUILD_DEPENDENCY_NJS_VERSION} + ENV BUILD_DEPENDENCY_QUICKJS_VERSION= + ENV BUILD_DEPENDENCY_QUICKJS_ROOT=/quickjs${BUILD_DEPENDENCY_QUICKJS_VERSION} + + USER root + + COPY --from=util /usr/local/bin/ /usr/local/bin + RUN set -ex; \ - CONFIG="\ - --with-cc-opt=-O2 \ - --prefix=/etc/nginx \ - --sbin-path=/usr/sbin/nginx \ - --modules-path=/usr/lib/nginx/modules \ - --conf-path=/etc/nginx/nginx.conf \ - --error-log-path=/var/log/nginx/error.log \ - --http-log-path=/var/log/nginx/access.log \ - --pid-path=${APP_ROOT}/run/nginx.pid \ - --lock-path=${APP_ROOT}/run/nginx.lock \ - --http-client-body-temp-path=${APP_ROOT}/cache/client_temp \ - --http-proxy-temp-path=${APP_ROOT}/cache/proxy_temp \ - --http-fastcgi-temp-path=${APP_ROOT}/cache/fastcgi_temp \ - --http-uwsgi-temp-path=${APP_ROOT}/cache/uwsgi_temp \ - --http-scgi-temp-path=${APP_ROOT}/cache/scgi_temp \ - --user=docker \ - --group=docker \ - --with-http_ssl_module \ - --with-http_realip_module \ - --with-http_addition_module \ - --with-http_sub_module \ - --with-http_dav_module \ - --with-http_flv_module \ - --with-http_mp4_module \ - --with-http_gunzip_module \ - --with-http_gzip_static_module \ - --with-http_random_index_module \ - --with-http_secure_link_module \ - --with-http_stub_status_module \ - --with-http_auth_request_module \ - --with-http_xslt_module=dynamic \ - --with-http_image_filter_module=dynamic \ - --with-http_geoip_module=dynamic \ - --with-threads \ - --with-stream \ - --with-stream_ssl_module \ - --with-stream_ssl_preread_module \ - --with-stream_realip_module \ - --with-stream_geoip_module=dynamic \ - --with-http_slice_module \ - --with-compat \ - --with-file-aio \ - --with-http_v2_module \ - --add-module=/usr/lib/nginx/modules/headers-more-nginx-module-${MODULE_HEADERS_MORE_NGINX_VERSION} \ - "; \ - apk add --no-cache --update \ - curl \ - tar \ - gcc \ - libc-dev \ - make \ - openssl-dev \ - pcre2-dev \ - zlib-dev \ - linux-headers \ - libxslt-dev \ - gd-dev \ - geoip-dev \ - perl-dev \ - libedit-dev \ - bash \ - alpine-sdk \ - findutils; \ - apk upgrade; \ - mkdir -p /usr/lib/nginx/modules; \ - mkdir -p /usr/src; \ - curl -SL https://github.com/openresty/headers-more-nginx-module/archive/v${MODULE_HEADERS_MORE_NGINX_VERSION}.tar.gz | tar -zxC /usr/lib/nginx/modules; \ - curl -SL https://nginx.org/download/nginx-${APP_VERSION}.tar.gz | tar -zxC /usr/src; \ - cd /usr/src/nginx-${APP_VERSION}; \ - ./configure $CONFIG --with-debug; \ - make -j $(nproc); \ - mv objs/nginx objs/nginx-debug; \ - mv objs/ngx_http_xslt_filter_module.so objs/ngx_http_xslt_filter_module-debug.so; \ - mv objs/ngx_http_image_filter_module.so objs/ngx_http_image_filter_module-debug.so; \ - mv objs/ngx_http_geoip_module.so objs/ngx_http_geoip_module-debug.so; \ - mv objs/ngx_stream_geoip_module.so objs/ngx_stream_geoip_module-debug.so; \ - ./configure $CONFIG; \ - make -j $(nproc); \ - make install; \ - install -m755 objs/ngx_http_xslt_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_xslt_filter_module-debug.so; \ - install -m755 objs/ngx_http_image_filter_module-debug.so /usr/lib/nginx/modules/ngx_http_image_filter_module-debug.so; \ - install -m755 objs/ngx_http_geoip_module-debug.so /usr/lib/nginx/modules/ngx_http_geoip_module-debug.so; \ - install -m755 objs/ngx_stream_geoip_module-debug.so /usr/lib/nginx/modules/ngx_stream_geoip_module-debug.so; \ - strip /usr/sbin/nginx*; \ - strip /usr/lib/nginx/modules/*.so; + apk --update --no-cache add \ + cmake \ + autoconf \ + automake \ + git \ + build-base \ + curl \ + tar \ + gcc \ + g++ \ + libc-dev \ + make \ + openssl-dev \ + pcre2-dev \ + zlib-dev \ + linux-headers \ + libxslt-dev \ + libxslt-static \ + gd-dev \ + geoip-dev \ + perl-dev \ + libedit-dev \ + libxml2-dev \ + libtool \ + quickjs-dev \ + quickjs-static \ + bash \ + libxml2-static \ + alpine-sdk \ + findutils \ + brotli-dev \ + libgd \ + tar \ + xz \ + upx; + + RUN set -ex; \ + cd /; \ + curl -SL https://nginx.org/download/nginx-${APP_VERSION}.tar.gz | tar -zxC /; \ + curl -SL https://zlib.net/fossils/zlib-${BUILD_DEPENDENCY_ZLIB_VERSION}.tar.gz | tar -zxC /; \ + curl -SL https://github.com/PCRE2Project/pcre2/releases/download/pcre2-${BUILD_DEPENDENCY_PCRE2_VERSION}/pcre2-${BUILD_DEPENDENCY_PCRE2_VERSION}.tar.gz | tar -zxC /; \ + curl -SL https://github.com/openresty/headers-more-nginx-module/archive/v${BUILD_DEPENDENCY_HEADERS_MORE_VERSION}.tar.gz | tar -zxC /; + + RUN set -ex; \ + #build OpenSSL + case "${APP_NGINX_CONFIGURATION}" in \ + "full") \ + cd /; \ + curl -SL https://github.com/openssl/openssl/releases/download/openssl-${BUILD_DEPENDENCY_OPENSSL_VERSION}/openssl-${BUILD_DEPENDENCY_OPENSSL_VERSION}.tar.gz | tar -zxC /; \ + ;; \ + esac; + + RUN set -ex; \ + # build brotli + cd /; \ + git clone --recurse-submodules -j8 https://github.com/google/ngx_brotli; \ + mkdir -p ${BUILD_DEPENDENCY_BROTLI_ROOT}/deps/brotli/out; \ + cd ${BUILD_DEPENDENCY_BROTLI_ROOT}/deps/brotli/out; \ + cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DCMAKE_C_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_CXX_FLAGS="-Ofast -m64 -march=native -mtune=native -flto -funroll-loops -ffunction-sections -fdata-sections -Wl,--gc-sections" -DCMAKE_INSTALL_PREFIX=./installed ..; \ + cmake --build . --config Release --target brotlienc; + + RUN set -ex; \ + #build QuickJS + case "${APP_NGINX_CONFIGURATION}" in \ + "full") \ + cd /; \ + git clone https://github.com/bellard/quickjs; \ + curl -SL https://github.com/nginx/njs/archive/refs/tags/${BUILD_DEPENDENCY_NJS_VERSION}.tar.gz | tar -zxC /; \ + cd ${BUILD_DEPENDENCY_QUICKJS_ROOT}; \ + CFLAGS='-fPIC -static -static-libgcc' make libquickjs.a; \ + ;; \ + esac; + + RUN set -ex; \ + #build XLST + case "${APP_NGINX_CONFIGURATION}" in \ + "full") \ + cd /; \ + curl -SL https://download.gnome.org/sources/libxml2/2.14/libxml2-2.14.1.tar.xz | tar -xJC /; \ + curl -SL https://download.gnome.org/sources/libxslt/1.1/libxslt-1.1.43.tar.xz | tar -xJC /; \ + cd /libxml2-2.14.1; \ + ./configure \ + --prefix="/usr" \ + --disable-shared \ + --enable-static \ + --without-python; \ + make -s -j $(nproc); \ + make install; \ + cd /libxslt-1.1.43; \ + ./configure \ + --prefix="/usr" \ + --disable-shared \ + --enable-static \ + --without-python; \ + make -s -j $(nproc); \ + make install; \ + ;; \ + esac; + + RUN set -ex; \ + case "${APP_NGINX_CONFIGURATION}" in \ + "light") \ + cd ${BUILD_ROOT}; \ + ./configure \ + --with-zlib=${BUILD_DEPENDENCY_ZLIB_ROOT} \ + --with-pcre=${BUILD_DEPENDENCY_PCRE2_ROOT} \ + --add-module=${BUILD_DEPENDENCY_HEADERS_MORE_ROOT} \ + --add-module=${BUILD_DEPENDENCY_BROTLI_ROOT} \ + --prefix=${NGINX_PREFIX} \ + --sbin-path=${BUILD_BIN} \ + --modules-path=${APP_ROOT}/lib/modules \ + --conf-path=${NGINX_PREFIX}/nginx.conf \ + --error-log-path=${APP_ROOT}/log/error.log \ + --http-log-path=${APP_ROOT}/log/access.log \ + --pid-path=${APP_ROOT}/run/nginx.pid \ + --lock-path=${APP_ROOT}/run/nginx.lock \ + --http-client-body-temp-path=${APP_ROOT}/cache/client_temp \ + --http-proxy-temp-path=${APP_ROOT}/cache/proxy_temp \ + --http-fastcgi-temp-path=${APP_ROOT}/cache/fastcgi_temp \ + --http-uwsgi-temp-path=${APP_ROOT}/cache/uwsgi_temp \ + --http-scgi-temp-path=${APP_ROOT}/cache/scgi_temp \ + --user=docker \ + --group=docker \ + --with-file-aio \ + --with-poll_module \ + --with-select_module \ + --with-http_addition_module \ + --with-http_dav_module \ + --with-http_flv_module \ + --with-http_gunzip_module \ + --with-http_gzip_static_module \ + --with-http_mp4_module \ + --with-http_realip_module \ + --with-http_stub_status_module \ + --with-http_sub_module \ + --with-http_v2_module \ + --without-http_autoindex_module \ + --without-http_browser_module \ + --without-http_charset_module \ + --without-http_empty_gif_module \ + --without-http_geo_module \ + --without-http_memcached_module \ + --without-http_map_module \ + --without-http_ssi_module \ + --without-http_split_clients_module \ + --without-http_fastcgi_module \ + --without-http_uwsgi_module \ + --without-http_userid_module \ + --without-http_scgi_module \ + --without-mail_pop3_module \ + --without-mail_imap_module \ + --without-mail_smtp_module \ + --with-cc-opt="-O2 -static -static-libgcc" \ + --with-ld-opt="-s -static"; \ + ;; \ + "full") \ + cd ${BUILD_ROOT}; \ + ./configure \ + --with-zlib=${BUILD_DEPENDENCY_ZLIB_ROOT} \ + --with-pcre=${BUILD_DEPENDENCY_PCRE2_ROOT} \ + --add-module=${BUILD_DEPENDENCY_HEADERS_MORE_ROOT} \ + --add-module=${BUILD_DEPENDENCY_BROTLI_ROOT} \ + --add-module=${BUILD_DEPENDENCY_NJS_ROOT}/nginx \ + --with-openssl=${BUILD_DEPENDENCY_OPENSSL_ROOT} \ + --prefix=${NGINX_PREFIX} \ + --sbin-path=${BUILD_BIN} \ + --modules-path=${APP_ROOT}/lib/modules \ + --conf-path=${NGINX_PREFIX}/nginx.conf \ + --error-log-path=${APP_ROOT}/log/error.log \ + --http-log-path=${APP_ROOT}/log/access.log \ + --pid-path=${APP_ROOT}/run/nginx.pid \ + --lock-path=${APP_ROOT}/run/nginx.lock \ + --http-client-body-temp-path=${APP_ROOT}/cache/client_temp \ + --http-proxy-temp-path=${APP_ROOT}/cache/proxy_temp \ + --http-fastcgi-temp-path=${APP_ROOT}/cache/fastcgi_temp \ + --http-uwsgi-temp-path=${APP_ROOT}/cache/uwsgi_temp \ + --http-scgi-temp-path=${APP_ROOT}/cache/scgi_temp \ + --user=docker \ + --group=docker \ + --with-http_ssl_module \ + --with-http_realip_module \ + --with-http_addition_module \ + --with-http_sub_module \ + --with-http_dav_module \ + --with-http_flv_module \ + --with-http_mp4_module \ + --with-http_gunzip_module \ + --with-http_gzip_static_module \ + --with-http_random_index_module \ + --with-http_secure_link_module \ + --with-http_stub_status_module \ + --with-http_auth_request_module \ + --with-http_geoip_module \ + --with-threads \ + --with-stream \ + --with-stream_ssl_module \ + --with-stream_ssl_preread_module \ + --with-stream_realip_module \ + --with-stream_geoip_module \ + --with-http_slice_module \ + --with-http_xslt_module \ + --with-mail \ + --with-mail_ssl_module \ + --with-compat \ + --with-file-aio \ + --with-http_v2_module \ + --with-cc-opt="-O2 -static -static-libgcc -I ${BUILD_DEPENDENCY_QUICKJS_ROOT}" \ + --with-ld-opt="-s -static -L ${BUILD_DEPENDENCY_QUICKJS_ROOT}"; \ + ;; \ + esac; + + RUN set -ex; \ + cd ${BUILD_ROOT}; \ + make -s -j $(nproc); \ + eleven checkStatic ${BUILD_BIN}; + + RUN set -ex; \ + mkdir -p /distroless/usr/local/bin; \ + mkdir -p /distroless${NGINX_PREFIX}; \ + cp -R ${BUILD_ROOT}/conf/* /distroless${NGINX_PREFIX}; \ + rm /distroless${NGINX_PREFIX}/nginx.conf; \ + eleven strip ${BUILD_BIN}; \ + cp ${BUILD_BIN} /distroless/usr/local/bin; + + COPY ./rootfs/etc /distroless/etc + +# :: Distroless / nginx + FROM scratch AS distroless-nginx + COPY --from=build /distroless/ / + + +# :: Build / file system + FROM alpine AS fs + ARG APP_ROOT + USER root + + RUN set -ex; \ + mkdir -p ${APP_ROOT}/etc; \ + mkdir -p ${APP_ROOT}/var; \ + mkdir -p ${APP_ROOT}/run; \ + mkdir -p ${APP_ROOT}/lib/modules; \ + mkdir -p ${APP_ROOT}/cache; \ + mkdir -p ${APP_ROOT}/log; \ + ln -sf /dev/stdout ${APP_ROOT}/log/access.log; \ + ln -sf /dev/stderr ${APP_ROOT}/log/error.log; + + COPY ./rootfs/nginx ${APP_ROOT} + +# :: Distroless / file system + FROM scratch AS distroless-fs + ARG APP_ROOT + COPY --from=fs ${APP_ROOT} /${APP_ROOT} + # :: Header - FROM 11notes/alpine:stable + FROM 11notes/distroless AS distroless + FROM 11notes/distroless:curl AS distroless-curl + FROM scratch # :: arguments ARG TARGETARCH @@ -111,50 +308,18 @@ ENV APP_VERSION=${APP_VERSION} ENV APP_ROOT=${APP_ROOT} - ENV NGINX_HEALTHCHECK_URL="https://localhost:8443/ping" - # :: multi-stage - COPY --from=util /usr/local/bin/ /usr/local/bin - COPY --from=build /usr/sbin/nginx /usr/sbin - COPY --from=build /etc/nginx/ /etc/nginx - COPY --from=build /usr/lib/nginx/modules/ /etc/nginx/modules - -# :: Run - USER root - RUN eleven printenv; - - # :: install application - RUN set -ex; \ - apk --no-cache --update add \ - inotify-tools \ - openssl \ - pcre2-dev; - - RUN set -ex; \ - eleven mkdir ${APP_ROOT}/{etc,var,ssl,cache,run}; \ - mkdir -p /var/log/nginx; \ - touch /var/log/nginx/access.log; \ - touch /var/log/nginx/error.log; \ - ln -sf /dev/stdout /var/log/nginx/access.log; \ - ln -sf /dev/stderr /var/log/nginx/error.log; - - # :: copy filesystem changes and set correct permissions - COPY ./rootfs / - RUN set -ex; \ - chmod +x -R /usr/local/bin; \ - chown -R 1000:1000 \ - ${APP_ROOT} \ - /var/log/nginx; - - # :: support unraid - RUN set -ex; \ - eleven unraid; + COPY --from=distroless --chown=${APP_UID}:${APP_GID} / / + COPY --from=distroless-fs --chown=${APP_UID}:${APP_GID} / / + COPY --from=distroless-curl --chown=${APP_UID}:${APP_GID} / / + COPY --from=distroless-nginx --chown=${APP_UID}:${APP_GID} / / # :: Volumes VOLUME ["${APP_ROOT}/etc", "${APP_ROOT}/var"] # :: Monitor - HEALTHCHECK --interval=5s --timeout=2s CMD curl -X GET -kILs --fail ${NGINX_HEALTHCHECK_URL} || exit 1 + HEALTHCHECK --interval=5s --timeout=2s CMD ["/usr/local/bin/curl", "-kILs", "--fail", "http://localhost:3000/ping"] # :: Start - USER docker \ No newline at end of file + USER ${APP_UID}:${APP_GID} + ENTRYPOINT ["/usr/local/bin/nginx"] \ No newline at end of file diff --git a/compose.yaml b/compose.yaml index 46f97f7..405749a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,17 +1,25 @@ +name: "nginx" services: nginx: - image: "11notes/nginx:1.26.2" - container_name: "nginx" + image: "11notes/nginx:1.26.3" + read_only: true environment: TZ: "Europe/Zurich" ports: - - "8443:8443/tcp" + - "3000:3000/tcp" + networks: + frontend: volumes: - "etc:/nginx/etc" - "var:/nginx/var" - - "ssl:/nginx/ssl" + tmpfs: + - "/nginx/cache:uid=1000,gid=1000" + - "/nginx/run:uid=1000,gid=1000" restart: "always" + volumes: etc: var: - ssl: \ No newline at end of file + +networks: + frontend: \ No newline at end of file diff --git a/project.md b/project.md index 4e9a0e0..fc047a0 100644 --- a/project.md +++ b/project.md @@ -1,7 +1,26 @@ -${{ content_synopsis }} What can I do with this? This image will serve as a base for nginx related images that need a high-performance webserver. It can also be used stand alone as a webserver or reverse proxy. It will automatically reload on config changes if configured. +${{ content_synopsis }} This image will serve as a base for nginx related images that need a high-performance webserver. The default tag of this image is stripped for most functions that can be used by a reverse proxy in front of nginx, it adds however important webserver functions like brotli compression. The default tag is not meant to run as a reverse proxy, use the full image for that. The default tag does not support HTTPS for instance! + +${{ content_uvp }} Good question! All the other images on the market that do exactly the same don’t do or offer these options: + +${{ github:> [!IMPORTANT] }} +${{ github:> }}* This image runs as 1000:1000 by default, most other images run everything as root +${{ github:> }}* This image has no shell since it is 100% distroless, most other images run on a distro like Debian or Alpine with full shell access (security) +${{ github:> }}* This image does not ship with any critical or high rated CVE and is automatically maintained via CI/CD, most other images mostly have no CVE scanning or code quality tools in place +${{ github:> }}* This image is created via a secure, pinned CI/CD process and immune to upstream attacks, most other images have upstream dependencies that can be exploited +${{ github:> }}* This image contains a proper health check that verifies the app is actually working, most other images have either no health check or only check if a port is open or ping works +${{ github:> }}* This image works as read-only, most other images need to write files to the image filesystem + +If you value security, simplicity and the ability to interact with the maintainer and developer of an image. Using my images is a great start in that direction. + +${{ title_config }} +```yaml +${{ include: ./rootfs/etc/nginx/nginx.conf }} +``` + +The default configuration contains no special settings. It enables brotli compression, sets the workers to the same amount as n-CPUs available, has two default logging formats, disables most stuff not needed and enables best performance settings. Please mount your own config if you need to change how nginx is setup. ${{ title_volumes }} -* **${{ json_root }}/etc** - Directory of vHost config, must end in *.conf (set in /etc/nginx/nginx.conf) +* **${{ json_root }}/etc** - Directory of vHost config, must end in *.conf * **${{ json_root }}/var** - Directory of webroot for vHost ${{ content_compose }} @@ -9,8 +28,6 @@ ${{ content_compose }} ${{ content_defaults }} ${{ content_environment }} -| `NGINX_DYNAMIC_RELOAD` | Enable reload of nginx on configuration changes in /nginx/etc (only on successful configuration test!) | | -| `NGINX_HEALTHCHECK_URL` | URL to check if nginx is ready to accept connections | https://localhost:8443/ping | ${{ content_source }} diff --git a/rootfs/etc/nginx/nginx.conf b/rootfs/etc/nginx/nginx.conf index 4743cde..03e4ff6 100644 --- a/rootfs/etc/nginx/nginx.conf +++ b/rootfs/etc/nginx/nginx.conf @@ -1,7 +1,8 @@ worker_processes auto; worker_cpu_affinity auto; worker_rlimit_nofile 204800; -error_log /var/log/nginx/error.log warn; +error_log /nginx/log/error.log warn; +daemon off; events { worker_connections 1024; @@ -25,6 +26,36 @@ http { tcp_nodelay on; gzip on; + brotli on; + brotli_comp_level 4; + brotli_static on; + brotli_types + text/plain + text/css + text/xml + text/javascript + text/x-component + application/xml + application/xml+rss + application/javascript + application/json + application/atom+xml + application/vnd.ms-fontobject + application/x-font-ttf + application/x-font-opentype + application/x-font-truetype + application/x-web-app-manifest+json + application/xhtml+xml + application/octet-stream + font/opentype + font/truetype + font/eot + font/otf + image/svg+xml + image/x-icon + image/vnd.microsoft.icon + image/bmp; + client_max_body_size 8M; keepalive_timeout 90; keepalive_requests 102400; @@ -37,5 +68,7 @@ http { open_file_cache_min_uses 2; open_file_cache_errors off; + root /nginx/var; + include /nginx/etc/*.conf; } \ No newline at end of file diff --git a/rootfs/nginx/etc/default.conf b/rootfs/nginx/etc/default.conf index b4bf6cd..0e52458 100644 --- a/rootfs/nginx/etc/default.conf +++ b/rootfs/nginx/etc/default.conf @@ -1,9 +1,10 @@ server { - listen 8443 default_server ssl; + listen 3000 default_server; server_name _; - ssl_certificate /nginx/ssl/default.crt; - ssl_certificate_key /nginx/ssl/default.key; + location / { + return 404; + } location /ping { return 200; diff --git a/rootfs/usr/local/bin/entrypoint.sh b/rootfs/usr/local/bin/entrypoint.sh deleted file mode 100644 index c8613cc..0000000 --- a/rootfs/usr/local/bin/entrypoint.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/ash - if { [ ! -f "${APP_ROOT}/ssl/default.crt" ] && [ -f "${APP_ROOT}/etc/default.conf" ] && cat ${APP_ROOT}/etc/default.conf | grep -q "default.crt"; }; then - eleven log debug "creating default certificate" - openssl req -x509 -newkey rsa:4096 -subj "/C=XX/ST=XX/L=XX/O=XX/OU=DOCKER/CN=${APP_NAME}" \ - -keyout "${APP_ROOT}/ssl/default.key" \ - -out "${APP_ROOT}/ssl/default.crt" \ - -days 3650 -nodes -sha256 &> /dev/null - fi - - if [ -z "${1}" ]; then - if [ ! -z ${NGINX_DYNAMIC_RELOAD} ]; then - eleven log info "enable dynamic reload" - /sbin/inotifyd /usr/local/bin/reload.sh ${APP_ROOT}/etc:cdnym & - fi - - set -- "nginx" \ - -g \ - 'daemon off;' - eleven log start - fi - - exec "$@" \ No newline at end of file diff --git a/rootfs/usr/local/bin/reload.sh b/rootfs/usr/local/bin/reload.sh deleted file mode 100644 index cd381fb..0000000 --- a/rootfs/usr/local/bin/reload.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/ash - eleven log debug "inotifyd event: ${1}" - eleven log info "reloading config" - NGINX_DYNAMIC_RELOAD_LOG=${APP_ROOT}/run/reload.log - nginx -t &> ${NGINX_DYNAMIC_RELOAD_LOG} - - while read -r LINE; do - if echo "${LINE}" | grep -q "nginx: "; then - if echo "${LINE}" | grep -q "\[warn\]"; then - LINE=$(echo ${LINE} | sed 's/nginx: \[warn\] //') - eleven log warning "${LINE}" - fi - - if echo "${LINE}" | grep -q "\[emerg\]"; then - LINE=$(echo ${LINE} | sed 's/nginx: \[emerg\] //') - eleven log error "${LINE}" - fi - fi - done < ${NGINX_DYNAMIC_RELOAD_LOG} - - if cat ${NGINX_DYNAMIC_RELOAD_LOG} | grep -q "test is successful"; then - nginx -s reload - eleven log info "config reloaded" - else - eleven log error "config reload failed!" - fi \ No newline at end of file