Skip to content

Conversation

@damienleger
Copy link
Contributor

  • 1.19-alpine to 1-alpine to ease the update process of nginx base image
  • remove unnecessary "apk update"
  • use the flag --chown and --chmod to avoid duplicate of files resulting
    of a "RUN chmod/chown" command: 20MB+ saved in the final docker image
  • non-root (nginx, uid=101) user as default for security best practice

Description

With #5476 RUN chmod have been added to make the docker container runnable on any non root user for Openshift. Those command create docker layer with files duplicate of 20MB+

This can be seen with https://github.com/wagoodman/dive (e.g. CI=true dive swaggerapi/swagger-ui:latest)

Result:

13:02 $ CI=true dive swaggerapi/swagger-ui:latest
  Using default CI config
Image Source: docker://swaggerapi/swagger-ui:latest
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 79.9446 %
  wastedBytes: 40172100 bytes (40 MB)
  userWastedPercent: 42.2440 %
Inefficient Files:
Count  Wasted Space  File Path
    2        8.6 MB  /usr/share/nginx/html/swagger-ui-bundle.js.map
    2        8.6 MB  /usr/share/nginx/html/swagger-ui-es-bundle.js.map
    2        3.8 MB  /usr/share/nginx/html/swagger-ui-es-bundle-core.js.map
    2        3.8 MB  /usr/share/nginx/html/swagger-ui.js.map
    2        2.8 MB  /usr/share/nginx/html/swagger-ui-standalone-preset.js.map
    2        2.7 MB  /var/cache/apk/APKINDEX.c77b2f80.tar.gz
    2        2.2 MB  /usr/share/nginx/html/swagger-ui-bundle.js
    2        2.2 MB  /usr/share/nginx/html/swagger-ui-es-bundle.js
    2        1.3 MB  /var/cache/apk/APKINDEX.32aecc44.tar.gz
    2        1.0 MB  /usr/share/nginx/html/swagger-ui.js
    2        1.0 MB  /usr/share/nginx/html/swagger-ui-es-bundle-core.js
    2        643 kB  /usr/share/nginx/html/swagger-ui-standalone-preset.js
    2        550 kB  /usr/share/nginx/html/swagger-ui.css.map
    2        428 kB  /etc/ssl/certs/ca-certificates.crt
    2        287 kB  /usr/share/nginx/html/swagger-ui.css
    3        205 kB  /lib/apk/db/installed
    3         58 kB  /lib/apk/db/scripts.tar
    2         10 kB  /etc/nginx/mime.types
    2        7.2 kB  /etc/nginx/win-utf
    2        7.2 kB  /usr/share/nginx/html/log.es-bundle-sizes.swagger-ui.txt
    2        7.2 kB  /usr/share/nginx/html/log.bundle-sizes.swagger-ui.txt
    2        6.7 kB  /usr/share/nginx/html/swagger-ui-es-bundle.js.LICENSE.txt
    2        6.7 kB  /usr/share/nginx/html/swagger-ui-bundle.js.LICENSE.txt
    2        5.7 kB  /etc/nginx/koi-utf
    2        5.2 kB  /usr/share/nginx/html/oauth2-redirect.html
    2        4.9 kB  /usr/share/nginx/configurator/translator.js
    2        4.4 kB  /etc/nginx/koi-win
    2        4.4 kB  /usr/share/nginx/configurator/variables.js
    2        3.9 kB  /usr/share/nginx/configurator/index.js
    3        3.5 kB  /usr/share/nginx/html/index.html
    2        3.4 kB  /usr/share/nginx/run.sh
    2        2.4 kB  /etc/passwd
    3        2.3 kB  /etc/nginx/nginx.conf
    2        2.2 kB  /etc/nginx/conf.d/default.conf
    2        2.2 kB  /etc/nginx/fastcgi.conf
    2        2.1 kB  /usr/share/nginx/configurator/oauth.js
    2        2.0 kB  /etc/nginx/fastcgi_params
    2        1.4 kB  /etc/group
    2        1.3 kB  /usr/share/nginx/html/favicon-16x16.png
    2        1.3 kB  /etc/nginx/uwsgi_params
    2        1.3 kB  /etc/nginx/scgi_params
    2        1.3 kB  /usr/share/nginx/html/favicon-32x32.png
    2        1.2 kB  /usr/share/nginx/html/swagger-ui-standalone-preset.js.LICENSE.txt
    2        1.1 kB  /etc/nginx/cors.conf
    2         988 B  /usr/share/nginx/html/50x.html
    2         871 B  /etc/shadow
    2         740 B  /usr/share/nginx/html/log.es-bundle-core-sizes.swagger-ui.txt
    3         572 B  /etc/apk/world
    2         542 B  /usr/share/nginx/configurator/helpers.js
    3         500 B  /lib/apk/db/triggers
    2         266 B  /usr/share/nginx/html/swagger-ui-es-bundle-core.js.LICENSE.txt
    2         266 B  /usr/share/nginx/html/swagger-ui.js.LICENSE.txt
    2           0 B  /var/cache/nginx
    2           0 B  /var/empty
    2           0 B  /var/lib/apk
    4           0 B  /var/cache/misc
    2           0 B  /tmp
    2           0 B  /var/tmp
    2           0 B  /var/lib/misc
    3           0 B  /lib/apk/db/lock
    2           0 B  /var/lib/udhcpd
    2           0 B  /var/local
    2           0 B  /var/lock/subsys
    2           0 B  /var/mail
    2           0 B  /var/opt
    2           0 B  /run
Results:
  FAIL: highestUserWastedPercent: too many bytes wasted, relative to the user bytes added (%-user-wasted-bytes=0.4224403872026876 > threshold=0.1)
  SKIP: highestWastedBytes: rule disabled
  FAIL: lowestEfficiency: image efficiency is too low (efficiency=0.7994464203961431 < threshold=0.9)
Result:FAIL [Total:3] [Passed:0] [Failed:2] [Warn:0] [Skipped:1]

My changes use buildx flag --chown / --chmod to keep the "run with any non root user" feature, but without create additional docker layer since there is no extra RUN command. Resulting in a smaller image. The chmod is done on directory only, which doesn't increase image size.

docker buildx build --tag=localbuild:1 --pull .
pull is necessary to ensure last version of 1-alpine from the remote registry is downloaded. This is to avoid update the FROM line in Dockerfile regularly.

Now with dive:

12:59 $ CI=true dive localbuild:1
  Using default CI config
Image Source: docker://localbuild:1
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 99.4256 %
  wastedBytes: 703334 bytes (703 kB)
  userWastedPercent: 0.9400 %
Inefficient Files:
Count  Wasted Space  File Path
    2        428 kB  /etc/ssl/certs/ca-certificates.crt
    3        206 kB  /lib/apk/db/installed
    3         60 kB  /lib/apk/db/scripts.tar
    2        2.4 kB  /etc/passwd
    2        2.1 kB  /usr/share/nginx/html/index.html
    2        1.5 kB  /etc/nginx/nginx.conf
    2        1.4 kB  /etc/group
    2         871 B  /etc/shadow
    3         562 B  /etc/apk/world
    3         500 B  /lib/apk/db/triggers
    2           0 B  /var/cache/nginx
    2           0 B  /tmp
    3           0 B  /var/cache/misc
    2           0 B  /run
    3           0 B  /lib/apk/db/lock
Results:
  PASS: highestUserWastedPercent
  SKIP: highestWastedBytes: rule disabled
  PASS: lowestEfficiency
Result:PASS [Total:3] [Passed:2] [Failed:0] [Warn:0] [Skipped:1]

Motivation and Context

Have a small docker image and follow run as non-root best practice reco.

How Has This Been Tested?

on Ubuntu 20.04.2 LTS server with docker community edition 20.10.8

build via
docker buildx build --tag=localbuild:1 --pull .

and tested with
docker run --rm -p 8080:8080 localbuild:1 to run on default nginx user
docker run --rm -p 8080:8080 -u nobody localbuild:1 to run on another (nobody) non root user

Screenshots (if appropriate):

Checklist

My PR contains...

  • No code changes (src/ is unmodified: changes to documentation, CI, metadata, etc.)
  • Dependency changes (any modification to dependencies in package.json)
  • Bug fixes (non-breaking change which fixes an issue)
  • Improvements (misc. changes to existing features)
  • Features (non-breaking change which adds functionality)

My changes...

  • are breaking changes to a public API (config options, System API, major UI change, etc).
  • are breaking changes to a private API (Redux, component props, utility functions, etc.).
  • are breaking changes to a developer API (npm script behavior changes, new dev system dependencies, etc).
  • are not breaking changes.

Documentation

  • My changes do not require a change to the project documentation.
  • My changes require a change to the project documentation.
  • If yes to above: I have updated the documentation accordingly.

Automated tests

  • My changes can not or do not need to be tested.
  • My changes can and should be tested by unit and/or integration tests.
  • If yes to above: I have added tests to cover my changes.
  • If yes to above: I have taken care to cover edge cases in my tests.
  • All new and existing tests passed.

- 1.19-alpine to 1-alpine to ease the update process of nginx base image
- remove unnecessary "apk update"
- use the flag --chown and --chmod to avoid duplicate of files resulting
  of a "RUN chmod/chown" command: 20MB+ saved in the final docker image
- non-root (nginx, uid=101) user as default for security best practice
@tim-lai
Copy link
Contributor

tim-lai commented Mar 21, 2022

@Blaimi can you take a look at this PR and share your opinion? Thanks in advance!

@tim-lai
Copy link
Contributor

tim-lai commented Mar 31, 2022

@damienleger thanks for the update, so you are still getting a smaller image even without using chown?

@damienleger
Copy link
Contributor Author

damienleger commented Apr 1, 2022

@tim-lai

@damienleger thanks for the update, so you are still getting a smaller image even without using chown?

Yes, 11MB smaller (less than before, but master changed a lot since then).
The main idea is to merge the COPY & RUN chmod commands into COPY --chmod commands to avoid layer duplicates.

Below you can see dive efficiency tests for current latest vs localbuild with modified Dockerfile. And resulting image size.

14:58 $ docker pull swaggerapi/swagger-ui:latest
Status: Downloaded newer image for swaggerapi/swagger-ui:latest
docker.io/swaggerapi/swagger-ui:latest

14:58 $ CI=true dive swaggerapi/swagger-ui:latest
  Using default CI config
Image Source: docker://swaggerapi/swagger-ui:latest
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 86.8188 %
  wastedBytes: 23011563 bytes (23 MB)
  userWastedPercent: 27.8719 %
Inefficient Files:
Count  Wasted Space  File Path
    2        3.3 MB  /var/cache/apk/APKINDEX.a754f5eb.tar.gz
    2        3.1 MB  /usr/share/nginx/html/swagger-ui-bundle.js.map
    2        3.1 MB  /usr/share/nginx/html/swagger-ui-es-bundle.js.map
    2        2.5 MB  /usr/share/nginx/html/swagger-ui-es-bundle-core.js.map
    2        2.2 MB  /usr/share/nginx/html/swagger-ui-bundle.js
    2        2.2 MB  /usr/share/nginx/html/swagger-ui-es-bundle.js
    2        1.3 MB  /var/cache/apk/APKINDEX.0488e555.tar.gz
    2        1.1 MB  /usr/share/nginx/html/swagger-ui-standalone-preset.js.map
    2        813 kB  /usr/share/nginx/html/swagger-ui-es-bundle-core.js
    2        679 kB  /usr/share/nginx/html/swagger-ui-standalone-preset.js
    2        606 kB  /usr/share/nginx/html/swagger-ui.js.map
    2        573 kB  /usr/share/nginx/html/swagger-ui.js
    2        553 kB  /usr/share/nginx/html/swagger-ui.css.map
    2        406 kB  /etc/ssl/certs/ca-certificates.crt
    2        288 kB  /usr/share/nginx/html/swagger-ui.css
    3        210 kB  /lib/apk/db/installed
    3         60 kB  /lib/apk/db/scripts.tar
    2         11 kB  /etc/nginx/mime.types
    2        7.2 kB  /usr/share/nginx/html/swagger-ui-es-bundle.js.LICENSE.txt
    2        7.2 kB  /usr/share/nginx/html/swagger-ui-bundle.js.LICENSE.txt
    2        5.5 kB  /usr/share/nginx/html/log.bundle-sizes.swagger-ui.txt
    2        5.5 kB  /usr/share/nginx/html/log.es-bundle-sizes.swagger-ui.txt
    2        5.4 kB  /usr/share/nginx/html/oauth2-redirect.html
    2        5.1 kB  /usr/share/nginx/configurator/translator.js
    2        4.6 kB  /usr/share/nginx/configurator/variables.js
    2        4.3 kB  /usr/share/nginx/configurator/index.js
    2        3.0 kB  /usr/share/nginx/html/swagger-ui-es-bundle-core.js.LICENSE.txt
    3        2.4 kB  /etc/nginx/nginx.conf
    2        2.4 kB  /etc/passwd
    2        2.3 kB  /usr/share/nginx/configurator/oauth.js
    2        2.2 kB  /etc/nginx/fastcgi.conf
    2        2.1 kB  /etc/nginx/conf.d/default.conf
    3        2.1 kB  /usr/share/nginx/html/index.html
    2        2.0 kB  /etc/nginx/fastcgi_params
    2        1.4 kB  /etc/group
    2        1.4 kB  /usr/share/nginx/html/swagger-ui-standalone-preset.js.LICENSE.txt
    2        1.3 kB  /usr/share/nginx/html/favicon-16x16.png
    2        1.3 kB  /etc/nginx/uwsgi_params
    2        1.3 kB  /etc/nginx/scgi_params
    2        1.3 kB  /usr/share/nginx/html/favicon-32x32.png
    2        1.1 kB  /etc/nginx/cors.conf
    2        1.1 kB  /usr/share/nginx/html/swagger-initializer.js
    2        1.0 kB  /usr/share/nginx/html/log.es-bundle-core-sizes.swagger-ui.txt
    2         994 B  /usr/share/nginx/html/50x.html
    2         871 B  /etc/shadow
    3         562 B  /etc/apk/world
    2         542 B  /usr/share/nginx/configurator/helpers.js
    3         500 B  /lib/apk/db/triggers
    2         404 B  /usr/share/nginx/html/index.css
    2           0 B  /var/lib/apk
    2           0 B  /var/lib/misc
    2           0 B  /var/lib/udhcpd
    4           0 B  /var/cache/misc
    2           0 B  /tmp
    2           0 B  /var/tmp
    2           0 B  /var/empty
    3           0 B  /lib/apk/db/lock
    2           0 B  /var/local
    2           0 B  /var/lock/subsys
    2           0 B  /run
    2           0 B  /var/mail
    2           0 B  /var/opt
    2           0 B  /var/cache/nginx
Results:
  FAIL: highestUserWastedPercent: too many bytes wasted, relative to the user bytes added (%-user-wasted-bytes=0.2787191456546064 > threshold=0.1)
  SKIP: highestWastedBytes: rule disabled
  FAIL: lowestEfficiency: image efficiency is too low (efficiency=0.8681882755428455 < threshold=0.9)
Result:FAIL [Total:3] [Passed:0] [Failed:2] [Warn:0] [Skipped:1]

14:58 $ docker buildx build --tag=localbuild:1 --pull .

14:59 $ CI=true dive localbuild:1
  Using default CI config
Image Source: docker://localbuild:1
Fetching image... (this can take a while for large images)
Analyzing image...
  efficiency: 99.4117 %
  wastedBytes: 685025 bytes (685 kB)
  userWastedPercent: 0.9596 %
Inefficient Files:
Count  Wasted Space  File Path
    2        406 kB  /etc/ssl/certs/ca-certificates.crt
    3        210 kB  /lib/apk/db/installed
    3         60 kB  /lib/apk/db/scripts.tar
    2        2.4 kB  /etc/passwd
    2        1.5 kB  /etc/nginx/nginx.conf
    2        1.4 kB  /etc/group
    2        1.3 kB  /usr/share/nginx/html/index.html
    2         871 B  /etc/shadow
    3         562 B  /etc/apk/world
    3         500 B  /lib/apk/db/triggers
    2           0 B  /var/cache/nginx
    2           0 B  /tmp
    3           0 B  /var/cache/misc
    2           0 B  /run
    3           0 B  /lib/apk/db/lock
Results:
  PASS: highestUserWastedPercent
  SKIP: highestWastedBytes: rule disabled
  PASS: lowestEfficiency
Result:PASS [Total:3] [Passed:2] [Failed:0] [Warn:0] [Skipped:1]


14:59 $ docker images | egrep 'swagger-ui|localbuild'
localbuild                                                            1                      8c9f0e87bd63   30 seconds ago   77MB
swaggerapi/swagger-ui                                                 latest                 a724e79bc771   4 days ago       88.2MB

@tim-lai tim-lai merged commit fb63df3 into swagger-api:master Apr 1, 2022
@tim-lai
Copy link
Contributor

tim-lai commented Apr 1, 2022

@damienleger PR merged! Thanks much for the continued followup to get this PR merged, and thanks for the contribution!

@damienleger damienleger deleted the improve_dockerfile branch April 1, 2022 17:27
@tim-lai
Copy link
Contributor

tim-lai commented Apr 1, 2022

@damienleger So some unfortunate news for this PR... it breaks SwaggerUI's Jenkins deployment jobs so I will have to revert it. Fyi, the issue is that the current Docker version used for deployment is old, and does not support either of

  1. COPY --chmod (only --chown) , per docs
  2. DOCKER_BUILDKIT=1 environment flag

I appreciate the effort on this PR, and it definitely would have been nice to trim a Docker layer. Hopefully we can revisit this topic in the future if/when deployment jobs versions get upgraded.

@tim-lai
Copy link
Contributor

tim-lai commented Apr 1, 2022

@damienleger Update, good news on this PR, the deployment configuration was updated by the Swagger DevOps team, so this PR remains intact and is included in v4.10.3 release!

@damienleger
Copy link
Contributor Author

Good news 🙂

FYI,

  1. the COPY --chmod requires Docker CE 20.10+ and is not documented at the moment.
  2. I personally prefer the more explicit buildx usage via the command docker buildx build over the DOCKER_BUILDKIT=1 envvar + docker build combination

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants