diff --git a/.github/actions/lint/action.yml b/.github/actions/lint/action.yml index dc326cc..44f7b4b 100644 --- a/.github/actions/lint/action.yml +++ b/.github/actions/lint/action.yml @@ -14,7 +14,8 @@ runs: - uses: hadolint/hadolint-action@v3.1.0 with: dockerfile: ${{ inputs.dockerfile-location }}/Dockerfile - ignore: SC1091,${{ inputs.ignore-linting-rules }} + ignore: ${{ inputs.ignore-linting-rules }} + - name: ShellCheck run: | cd ${{ inputs.dockerfile-location }} diff --git a/.github/actions/push/action.yml b/.github/actions/push/action.yml index ef030ea..382485b 100644 --- a/.github/actions/push/action.yml +++ b/.github/actions/push/action.yml @@ -1,4 +1,4 @@ -name: Push Docker image +name: Build and push Docker images inputs: image-id: @@ -36,10 +36,13 @@ runs: registry: ghcr.io username: ${{ github.actor }} password: ${{ inputs.github-token }} + - name: Set up QEMU uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + - name: Build and push image uses: docker/build-push-action@v6 with: @@ -56,9 +59,11 @@ runs: push: true tags: ${{ inputs.image-id }}:${{ inputs.image-tag }} provenance: false + - name: List manifest run: docker buildx imagetools inspect ${{ inputs.image-id }}:${{ inputs.image-tag }} shell: bash + - name: Build and push versioned and latest image tags if: startsWith(github.ref, 'refs/tags/') uses: docker/build-push-action@v6 diff --git a/.github/actions/scan/action.yml b/.github/actions/scan/action.yml index 05e2097..5676db8 100644 --- a/.github/actions/scan/action.yml +++ b/.github/actions/scan/action.yml @@ -18,6 +18,7 @@ runs: format: "sarif" output: "trivy-results.sarif" ignore-unfixed: true + - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 with: diff --git a/.github/actions/build/action.yml b/.github/actions/test-build/action.yml similarity index 86% rename from .github/actions/build/action.yml rename to .github/actions/test-build/action.yml index 7901004..1296e31 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/test-build/action.yml @@ -1,4 +1,4 @@ -name: Build Docker images +name: Test Docker image build inputs: image-name: @@ -15,17 +15,6 @@ inputs: type: string default: app -outputs: - image-id: - value: ${{ steps.generate-tags.outputs.image-id }} - tag: - description: "The generated tag" - value: ${{ steps.generate-tags.outputs.tag }} - major-version: - value: ${{ steps.generate-tags.outputs.major-version }} - minor-version: - value: ${{ steps.generate-tags.outputs.minor-version }} - runs: using: "composite" steps: @@ -48,15 +37,11 @@ runs: echo "major-version=$MAJOR_VERSION" >> "$GITHUB_OUTPUT" echo "minor-version=$MINOR_VERSION" >> "$GITHUB_OUTPUT" shell: bash - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Build images uses: docker/build-push-action@v6 with: context: ${{ inputs.dockerfile-location }} - platforms: linux/amd64,linux/arm64 labels: | runnumber=${{ github.run_id }} build-args: | @@ -66,8 +51,17 @@ runs: TNA_DOCKER_IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }}/blob/main/${{ inputs.dockerfile-location }}/Dockerfile USER_IMAGE=${{ inputs.user-image }} push: false + load: true tags: ${{ steps.generate-tags.outputs.image-id }}:${{ steps.generate-tags.outputs.tag }} provenance: false - - name: List manifest - run: docker buildx imagetools inspect ${{ steps.generate-tags.outputs.image-id }}:${{ steps.generate-tags.outputs.tag }} - shell: bash + +outputs: + image-id: + value: ${{ steps.generate-tags.outputs.image-id }} + tag: + description: "The generated tag" + value: ${{ steps.generate-tags.outputs.tag }} + major-version: + value: ${{ steps.generate-tags.outputs.major-version }} + minor-version: + value: ${{ steps.generate-tags.outputs.minor-version }} diff --git a/.github/actions/test/action.yml b/.github/actions/test-container/action.yml similarity index 98% rename from .github/actions/test/action.yml rename to .github/actions/test-container/action.yml index 94480eb..46b1413 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test-container/action.yml @@ -1,4 +1,4 @@ -name: Test the Docker image user +name: Test Docker image inputs: application-repository: @@ -25,6 +25,7 @@ runs: with: repository: ${{ inputs.application-repository }} path: ${{ inputs.application-repository }} + - name: Start application run: | cd ${{ inputs.application-repository }} @@ -35,9 +36,11 @@ runs: echo "PORT=$PORT" >> "$GITHUB_ENV" docker compose up ${{ inputs.service }} -d shell: bash + - name: Check healthcheck endpoint run: for c in {1..30}; do sleep 1 && curl -s -w '%{http_code}' -o /dev/null http://localhost:${{ env.PORT }}/healthcheck/live/ | grep -o "200" && break; done shell: bash + - name: Check user run: | cd ${{ inputs.application-repository }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4fb7e4..0081e34 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build, test and publish +name: Build, test and push permissions: contents: read @@ -28,6 +28,7 @@ jobs: python: name: Python runs-on: ubuntu-latest + timeout-minutes: 15 env: IMAGE_NAME: tna-python DOCKERFILE_LOCATION: docker/tna-python @@ -36,36 +37,40 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Lint + + - name: Lint Dockerfile uses: ./.github/actions/lint with: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} - ignore-linting-rules: DL3002,DL3006 - - name: Build + + - name: Test Docker image build id: build - uses: ./.github/actions/build + uses: ./.github/actions/test-build with: image-name: ${{ env.IMAGE_NAME }} base-image: ${{ env.BASE_IMAGE }} dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} + - name: Test Flask - uses: ./.github/actions/test + uses: ./.github/actions/test-container with: application-repository: nationalarchives/flask-application-template service: app image: ${{ env.IMAGE_NAME }} image-tag: ${{ steps.build.outputs.tag }} expected-user: app + - name: Test FastAPI - uses: ./.github/actions/test + uses: ./.github/actions/test-container with: application-repository: nationalarchives/fastapi-application-template service: app image: ${{ env.IMAGE_NAME }} image-tag: ${{ steps.build.outputs.tag }} expected-user: app - - name: Push + + - name: Build and push Docker images uses: ./.github/actions/push with: image-id: ${{ steps.build.outputs.image-id }} @@ -76,7 +81,8 @@ jobs: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Scan + + - name: Scan Docker image uses: ./.github/actions/scan with: image-id: ${{ steps.build.outputs.image-id }} @@ -85,6 +91,7 @@ jobs: python-root: name: Python (root) runs-on: ubuntu-latest + timeout-minutes: 15 env: IMAGE_NAME: tna-python-root DOCKERFILE_LOCATION: docker/tna-python @@ -93,36 +100,41 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Lint + + - name: Lint Dockerfile uses: ./.github/actions/lint with: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} - ignore-linting-rules: DL3002,DL3006 - - name: Build + ignore-linting-rules: DL3002 + + - name: Test Docker image build id: build - uses: ./.github/actions/build + uses: ./.github/actions/test-build with: image-name: ${{ env.IMAGE_NAME }} base-image: ${{ env.BASE_IMAGE }} dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} + - name: Test Flask - uses: ./.github/actions/test + uses: ./.github/actions/test-container with: application-repository: nationalarchives/flask-application-template service: app image: ${{ env.IMAGE_NAME }} image-tag: ${{ steps.build.outputs.tag }} expected-user: root + - name: Test FastAPI - uses: ./.github/actions/test + uses: ./.github/actions/test-container with: application-repository: nationalarchives/fastapi-application-template service: app image: ${{ env.IMAGE_NAME }} image-tag: ${{ steps.build.outputs.tag }} expected-user: root - - name: Push + + - name: Build and push Docker images uses: ./.github/actions/push with: image-id: ${{ steps.build.outputs.image-id }} @@ -133,7 +145,8 @@ jobs: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Scan + + - name: Scan Docker image uses: ./.github/actions/scan with: image-id: ${{ steps.build.outputs.image-id }} @@ -143,6 +156,7 @@ jobs: name: Python Django needs: python runs-on: ubuntu-latest + timeout-minutes: 5 env: IMAGE_NAME: tna-python-django DOCKERFILE_LOCATION: docker/tna-python-django @@ -151,27 +165,31 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Lint + + - name: Lint Dockerfile uses: ./.github/actions/lint with: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} - - name: Build + + - name: Test Docker image build id: build - uses: ./.github/actions/build + uses: ./.github/actions/test-build with: image-name: ${{ env.IMAGE_NAME }} base-image: ${{ env.BASE_IMAGE }} dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} + - name: Test Django - uses: ./.github/actions/test + uses: ./.github/actions/test-container with: application-repository: nationalarchives/django-application-template service: app image: ${{ env.IMAGE_NAME }} image-tag: ${{ steps.build.outputs.tag }} expected-user: app - - name: Push + + - name: Build and push Docker images uses: ./.github/actions/push with: image-id: ${{ steps.build.outputs.image-id }} @@ -182,7 +200,8 @@ jobs: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Scan + + - name: Scan Docker image uses: ./.github/actions/scan with: image-id: ${{ steps.build.outputs.image-id }} @@ -192,6 +211,7 @@ jobs: name: Python Django (root) needs: python-root runs-on: ubuntu-latest + timeout-minutes: 5 env: IMAGE_NAME: tna-python-django-root DOCKERFILE_LOCATION: docker/tna-python-django @@ -200,28 +220,32 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Lint + + - name: Lint Dockerfile uses: ./.github/actions/lint with: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} ignore-linting-rules: DL3002 - - name: Build + + - name: Test Docker image build id: build - uses: ./.github/actions/build + uses: ./.github/actions/test-build with: image-name: ${{ env.IMAGE_NAME }} base-image: ${{ env.BASE_IMAGE }} dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} + - name: Test Django - uses: ./.github/actions/test + uses: ./.github/actions/test-container with: application-repository: nationalarchives/django-application-template service: app image: ${{ env.IMAGE_NAME }} image-tag: ${{ steps.build.outputs.tag }} expected-user: root - - name: Push + + - name: Build and push Docker images uses: ./.github/actions/push with: image-id: ${{ steps.build.outputs.image-id }} @@ -232,7 +256,8 @@ jobs: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Scan + + - name: Scan Docker image uses: ./.github/actions/scan with: image-id: ${{ steps.build.outputs.image-id }} @@ -242,6 +267,7 @@ jobs: name: Python Dev needs: python-root runs-on: ubuntu-latest + timeout-minutes: 5 env: IMAGE_NAME: tna-python-dev DOCKERFILE_LOCATION: docker/tna-python-dev @@ -250,20 +276,23 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Lint + + - name: Lint Dockerfile uses: ./.github/actions/lint with: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} - ignore-linting-rules: DL3002,DL3006 - - name: Build + ignore-linting-rules: DL3002 + + - name: Test Docker image build id: build - uses: ./.github/actions/build + uses: ./.github/actions/test-build with: image-name: ${{ env.IMAGE_NAME }} base-image: ${{ env.BASE_IMAGE }} dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} - - name: Push + + - name: Build and push Docker images uses: ./.github/actions/push with: image-id: ${{ steps.build.outputs.image-id }} @@ -274,7 +303,8 @@ jobs: dockerfile-location: ${{ env.DOCKERFILE_LOCATION }} user-image: ${{ env.USER_IMAGE }} github-token: ${{ secrets.GITHUB_TOKEN }} - - name: Scan + + - name: Scan Docker image uses: ./.github/actions/scan with: image-id: ${{ steps.build.outputs.image-id }} diff --git a/.github/workflows/branch-cleanup.yml b/.github/workflows/cleanup-branches.yml similarity index 99% rename from .github/workflows/branch-cleanup.yml rename to .github/workflows/cleanup-branches.yml index 2aadd2f..b8e98ca 100644 --- a/.github/workflows/branch-cleanup.yml +++ b/.github/workflows/cleanup-branches.yml @@ -27,8 +27,10 @@ jobs: # Strip git ref prefix from version VERSION=$(echo "${{ github.event.ref }}" | sed -e 's,.*/\(.*\),\1,') echo "TAG=$VERSION" >> "$GITHUB_ENV" + - name: Output image tag run: echo "Clean up Docker image ${{ env.IMAGE_ID }}:${{ env.TAG }}" + - name: Delete image uses: bots-house/ghcr-delete-image-action@v1.1.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index f27978d..0a4131e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- SSL certificates must be generated and used on environments outside of production + ### Changed - Set the `default` alias for nvm to `lts/jod` @@ -17,11 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +- Removed `lts/iron` + ### Fixed +- Fixed the default NodeJS version in `tna-python-dev` + ### Security -- Updated `curl` and `libcurl4` to `7.88.1-10+deb12u7` ([CVE-2024-2004](https://avd.aquasec.com/nvd/2024/cve-2024-2004/), [CVE-2024-2398](https://avd.aquasec.com/nvd/2024/cve-2024-2398/), [CVE-2024-7264](https://avd.aquasec.com/nvd/cve-2024-7264)) +- Updated `curl` and `libcurl4` to `7.88.1-10+deb12u8` (fixes [CVE-2024-2004](https://avd.aquasec.com/nvd/2024/cve-2024-2004/), [CVE-2024-2398](https://avd.aquasec.com/nvd/2024/cve-2024-2398/) and [CVE-2024-7264](https://avd.aquasec.com/nvd/cve-2024-7264)) ## [0.4.0](https://github.com/nationalarchives/docker/compare/v0.3.0...v0.4.0) - 2024-11-04 diff --git a/docker/tna-python-dev/Dockerfile b/docker/tna-python-dev/Dockerfile index ab6a94e..27d6e39 100644 --- a/docker/tna-python-dev/Dockerfile +++ b/docker/tna-python-dev/Dockerfile @@ -12,18 +12,6 @@ FROM "$BASE_IMAGE":"$BASE_IMAGE_TAG" # ========================================== SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# RUN apt-get update; \ -# apt-get install -y --no-install-recommends ca-certificates curl gnupg; \ -# install -m 0755 -d /etc/apt/keyrings; \ -# curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg; \ -# chmod a+r /etc/apt/keyrings/docker.gpg; \ -# echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null; \ -# apt-get update; \ -# apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; \ -# apt-get clean; \ -# apt-get autoremove -y --purge; \ -# rm -rfv /var/lib/apt/lists/* - # ========================================== # Copy the development scripts into the user # .local/bin/dev/ directory diff --git a/docker/tna-python-dev/bin/dev b/docker/tna-python-dev/bin/dev index dfe3dc6..a542c1b 100755 --- a/docker/tna-python-dev/bin/dev +++ b/docker/tna-python-dev/bin/dev @@ -10,7 +10,7 @@ if [ -f "/app/.nvmrc" ] then nvm install else - nvm use lts/iron + nvm use lts/jod fi npm install -g prettier@3.3.3 eslint@8.56.0 stylelint@16.10.0 stylelint-config-standard-scss@13.1.0 stylelint-selector-bem-pattern@4.0.1 diff --git a/docker/tna-python/Dockerfile b/docker/tna-python/Dockerfile index cd37a00..d05f62a 100644 --- a/docker/tna-python/Dockerfile +++ b/docker/tna-python/Dockerfile @@ -19,9 +19,10 @@ LABEL org.opencontainers.image.description="National Archives base Docker image" org.opencontainers.image.licenses=MIT # ========================================== -# All of our Python containers should expose -# port 8080, which can then be mapped to any -# other port on the host system +# All TNA containers should expose identical +# ports which will then be mapped to another +# port on the host system - we use port 8080 +# for all our applications # ========================================== EXPOSE 8080/tcp @@ -92,15 +93,12 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] # ========================================== # Update the package index files and install -# a specific version of curl that we know to -# have no issues which is required later for -# installing Poetry and create a new system- -# level nonroot user who has with a new home -# directory where we will be able to install -# libraries into (along with our scripts for -# building and running) and create an "/app" -# directory in the root (for the application -# code) +# specific versions of libcurl4, curl, build +# essentials (which then enables us to later +# install Poetry) after which go on to clean +# and remove all the apt registries to avoid +# the possibility of additional applications +# being installed later on # ========================================== RUN set -eux; \ \ @@ -108,15 +106,26 @@ RUN set -eux; \ \ apt-get update; \ apt-get -y upgrade; \ - apt-get install -y --no-install-recommends libcurl4=7.88.1-10+deb12u7 curl=7.88.1-10+deb12u7 build-essential=12.9 libmagic-dev=1:5.44-3; \ + apt-get install -y --no-install-recommends libcurl4=7.88.1-10+deb12u8 curl=7.88.1-10+deb12u8 build-essential=12.9 libmagic-dev=1:5.44-3; \ \ apt-get clean; \ apt-get autoremove -y --purge; \ - rm -rfv /var/lib/apt/lists/*; \ - \ - useradd --system --create-home app; \ - \ - mkdir -p /app; \ + rm -rfv /var/lib/apt/lists/* + +# ========================================== +# Create a new system-level nonroot user who +# owns a home directory where we can install +# libraries into (along with our scripts for +# building and running) +# ========================================== +RUN useradd --system --create-home app + +# ========================================== +# Create an "/app" directory in the root dir +# for the application code and allow the new +# app user to access it +# ========================================== +RUN mkdir -p /app; \ chown app:app -R /app; \ chmod 700 /app @@ -170,20 +179,25 @@ USER app +# hadolint ignore=DL3006 FROM "$USER_IMAGE" # ========================================== # Install the latest LTS version of Node.js, # but keeping within the releases code-named # "jod" (v22.x) -# (Install the previous LTS release, just in -# case some applicaitons still use it) # ========================================== +# hadolint ignore=SC1091 RUN . "$NVM_DIR/nvm.sh"; \ - nvm install lts/iron; \ nvm install lts/jod; \ nvm alias default lts/jod +# ========================================== +# Create a directory for our SSL certificate +# files +# ========================================== +RUN mkdir /home/app/ssl + # ========================================== # Into our .local/bin/ directory copy in the # bash scripts we need in order to build and diff --git a/docker/tna-python/README.md b/docker/tna-python/README.md index fbcc8d1..444e9fd 100644 --- a/docker/tna-python/README.md +++ b/docker/tna-python/README.md @@ -15,32 +15,40 @@ This image requires you have the following files in the root of your project: ## Environment variables -| Variable | Description | Production default | Staging default | Develop default | Other default | -| --------------------- | ------------------------------------------------------------------------- | --------------------- | --------------------- | --------------------- | --------------------- | -| `ENVIRONMENT` | The current environment[^1] | `production` | `staging` | `develop` | _none_ | -| `SECRET_KEY` | A random key used to secure client session data | _none_ | _none_ | _none_ | _none_ | -| `WORKERS` | Number of worker processes[^2] | `(cpu * 2) + 1` | `(cpu * 2) + 1` | `3` | `(cpu * 2) + 1` | -| `THREADS` | Number of threads[^3] | `(cpu * 2) + 1` | `(cpu * 2) + 1` | `3` | `(cpu * 2) + 1` | -| `LOG_LEVEL` | The log level to stream to the console[^4] | `warn` | `debug` | `debug` | `info` | -| `NODE_ENV` | The node environment[^5] | Mirrors `ENVIRONMENT` | Mirrors `ENVIRONMENT` | Mirrors `ENVIRONMENT` | Mirrors `ENVIRONMENT` | -| `NPM_BUILD_COMMAND` | The npm script to run to build static assets | _none_ | _none_ | _none_ | _none_ | -| `NPM_DEVELOP_COMMAND` | The npm script to run in development environments | _ignored_ | _ignored_ | _none_ | _ignored_ | -| `TIMEOUT` | The number of seconds before a request is terminated[^6] | `30` | `30` | `600` | `30` | -| `KEEP_ALIVE` | The number of seconds to wait for requests on a keep-alive connection[^7] | `30` | `30` | `5` | `5` | +The three default environment names are: -[^1]: Predefined values are `production` and `develop` but any alphanumeric string is valid +- `production` +- `staging` +- `develop` -[^2]: [Gunicorn docs - How Many Workers?](https://docs.gunicorn.org/en/latest/design.html#how-many-workers) +Any other alphanumeric string is considered a valid environment name but won't have predefined settings. -[^3]: [Gunicorn docs - How Many Threads?](https://docs.gunicorn.org/en/latest/design.html#how-many-threads) +| Variable | Description | Production default | Staging default | Develop default | Custom env | +| ---------------------- | ------------------------------------------------------------------------- | ------------------------ | ------------------------ | ------------------------ | ------------------------ | +| `SECRET_KEY` | A random key used to secure client session data | _none_ | _none_ | _none_ | _none_ | +| `WORKERS` | Number of worker processes[^1] | `(cpu * 2) + 1` | `(cpu * 2) + 1` | `3` | `(cpu * 2) + 1` | +| `THREADS` | Number of threads[^2] | `(cpu * 2) + 1` | `(cpu * 2) + 1` | `3` | `(cpu * 2) + 1` | +| `LOG_LEVEL` | The log level to stream to the console[^3] | `warn` | `debug` | `debug` | `info` | +| `NODE_ENV` | The node environment[^4] | Mirrors `ENVIRONMENT` | Mirrors `ENVIRONMENT` | Mirrors `ENVIRONMENT` | Mirrors `ENVIRONMENT` | +| `NPM_BUILD_COMMAND` | The npm script to run to build static assets | _none_ | _none_ | _none_ | _none_ | +| `NPM_DEVELOP_COMMAND` | The npm script to run in development environments | _ignored_ | _ignored_ | _none_ | _ignored_ | +| `TIMEOUT` | The number of seconds before a request is terminated[^5] | `30` | `30` | `600` | `30` | +| `KEEP_ALIVE` | The number of seconds to wait for requests on a keep-alive connection[^6] | `30` | `30` | `5` | `5` | +| `SSL_KEY_FILE` | The location of the SSL key | `/home/app/ssl/key.pem` | `/home/app/ssl/key.pem` | `/home/app/ssl/key.pem` | `/home/app/ssl/key.pem` | +| `SSL_CERTIFICATE_FILE` | The location of the SSL certificate | `/home/app/ssl/cert.pem` | `/home/app/ssl/cert.pem` | `/home/app/ssl/cert.pem` | `/home/app/ssl/cert.pem` | +| `ALLOW_INSECURE` | If `true`, allow servers above development to run HTTP rather than HTTPS | `false` | `false` | _ignored_ | `false` | -[^4]: Supported levels are `critical`, `error`, `warn`, `info` and `debug` [Gunicorn docs - log level](https://docs.gunicorn.org/en/latest/settings.html?highlight=log#loglevel) +[^1]: [Gunicorn docs - How Many Workers?](https://docs.gunicorn.org/en/latest/design.html#how-many-workers) -[^5]: [Node.js, the difference between development and production](https://nodejs.dev/en/learn/nodejs-the-difference-between-development-and-production/) +[^2]: [Gunicorn docs - How Many Threads?](https://docs.gunicorn.org/en/latest/design.html#how-many-threads) -[^6]: [Gunicorn docs - timeout](https://docs.gunicorn.org/en/stable/settings.html#timeout) +[^3]: Supported levels are `critical`, `error`, `warn`, `info` and `debug` [Gunicorn docs - log level](https://docs.gunicorn.org/en/latest/settings.html?highlight=log#loglevel) -[^7]: [Gunicorn docs - keepalive](https://docs.gunicorn.org/en/stable/settings.html#keepalive) +[^4]: [Node.js, the difference between development and production](https://nodejs.dev/en/learn/nodejs-the-difference-between-development-and-production/) + +[^5]: [Gunicorn docs - timeout](https://docs.gunicorn.org/en/stable/settings.html#timeout) + +[^6]: [Gunicorn docs - keepalive](https://docs.gunicorn.org/en/stable/settings.html#keepalive) ### Secret key @@ -115,3 +123,20 @@ To use Node to build your assets you need three files in your project: - `package.json` - `package-lock.json` - `.nvmrc` containing the version of Node you would like to support (e.g. `lts/iron`) + +## SSL + +On all environments apart from `develop`, an SSL certificate is required. + +Two files need to be mounted to the container in order to run environments outside of development: + +- `/home/app/ssl/key.pem` +- `/home/app/ssl/cert.pem` + +Ensure the files can be read by the container user. + +These locations can be overridden with the `SSL_KEY_FILE` and `SSL_CERTIFICATE_FILE` environment variables. + +### Disabling SSL + +Although not recommended, SSL can be disabled on higher environments by setting `ALLOW_INSECURE` to `true`. diff --git a/docker/tna-python/bin/tna-run b/docker/tna-python/bin/tna-run index 03e3c80..f3cc0b3 100755 --- a/docker/tna-python/bin/tna-run +++ b/docker/tna-python/bin/tna-run @@ -52,19 +52,7 @@ DEFAULT_THREADS=$((DEFAULT_WORKERS * 2)) [[ -z $WORKERS ]] && WORKERS=$DEFAULT_WORKERS [[ -z $THREADS ]] && THREADS=$DEFAULT_THREADS -if [ "$ENVIRONMENT" == 'production' ] -then - # Production environment - [[ -z $LOG_LEVEL ]] && LOG_LEVEL=warn - [[ -z $TIMEOUT ]] && TIMEOUT=30 - [[ -z $KEEP_ALIVE ]] && KEEP_ALIVE=30 -elif [ "$ENVIRONMENT" == 'staging' ] -then - # Staging environment - [[ -z $LOG_LEVEL ]] && LOG_LEVEL=debug - [[ -z $TIMEOUT ]] && TIMEOUT=30 - [[ -z $KEEP_ALIVE ]] && KEEP_ALIVE=30 -elif [ "$ENVIRONMENT" == 'develop' ] +if [ "$ENVIRONMENT" == 'develop' ] then # Development environment echo "ENVIRONMENT is develop" @@ -102,8 +90,20 @@ then echo "FastAPI not found" # Fall back to using Gunicorn - echo "No framework found, using Gunicorn to serve development application" + echo "No framework found, using Gunicorn to serve development application..." poetry run gunicorn "$APPLICATION" --workers "$WORKERS" --threads "$THREADS" --log-level "$LOG_LEVEL" --timeout "$TIMEOUT" --keep-alive "$KEEP_ALIVE" --bind 0.0.0.0:8080 --worker-class="$WORKER_CLASS" --reload +elif [ "$ENVIRONMENT" == 'production' ] +then + # Production environment + [[ -z $LOG_LEVEL ]] && LOG_LEVEL=warn + [[ -z $TIMEOUT ]] && TIMEOUT=30 + [[ -z $KEEP_ALIVE ]] && KEEP_ALIVE=30 +elif [ "$ENVIRONMENT" == 'staging' ] +then + # Staging environment + [[ -z $LOG_LEVEL ]] && LOG_LEVEL=debug + [[ -z $TIMEOUT ]] && TIMEOUT=30 + [[ -z $KEEP_ALIVE ]] && KEEP_ALIVE=30 else # All other environments [[ -z $LOG_LEVEL ]] && LOG_LEVEL=info @@ -111,6 +111,30 @@ else [[ -z $KEEP_ALIVE ]] && KEEP_ALIVE=5 fi -# Start the server -echo "Starting $ENVIRONMENT server" -poetry run gunicorn "$APPLICATION" --workers "$WORKERS" --threads "$THREADS" --log-level "$LOG_LEVEL" --timeout "$TIMEOUT" --keep-alive "$KEEP_ALIVE" --access-logfile - --bind 0.0.0.0:8080 --worker-class="$WORKER_CLASS" +[[ -z $ALLOW_INSECURE ]] && ALLOW_INSECURE='false' + +if [ "$ALLOW_INSECURE" = 'true' ] +then + # Start the server + echo "Starting $ENVIRONMENT server (insecure)..." + poetry run gunicorn "$APPLICATION" --workers "$WORKERS" --threads "$THREADS" --log-level "$LOG_LEVEL" --timeout "$TIMEOUT" --keep-alive "$KEEP_ALIVE" --access-logfile - --bind 0.0.0.0:8080 --worker-class="$WORKER_CLASS" +else + [[ -z $SSL_KEY_FILE ]] && SSL_KEY_FILE=/home/app/ssl/key.pem + [[ -z $SSL_CERTIFICATE_FILE ]] && SSL_CERTIFICATE_FILE=/home/app/ssl/cert.pem + + # Check for SSL certificates + if [ ! -f "$SSL_KEY_FILE" ] + then + echo "$SSL_KEY_FILE does not exist"; + exit 1 + fi + if [ ! -f "$SSL_CERTIFICATE_FILE" ] + then + echo "$SSL_CERTIFICATE_FILE does not exist"; + exit 1 + fi + + # Start the server + echo "Starting $ENVIRONMENT server..." + poetry run gunicorn "$APPLICATION" --workers "$WORKERS" --threads "$THREADS" --log-level "$LOG_LEVEL" --timeout "$TIMEOUT" --keep-alive "$KEEP_ALIVE" --access-logfile - --bind 0.0.0.0:8080 --worker-class="$WORKER_CLASS" --keyfile="$SSL_KEY_FILE" --certfile="$SSL_CERTIFICATE_FILE" +fi