Skip to content

Commit

Permalink
feat: Provide helm chart
Browse files Browse the repository at this point in the history
  • Loading branch information
PSanetra committed Aug 9, 2024
1 parent cf6f9b1 commit 73aa4bd
Show file tree
Hide file tree
Showing 37 changed files with 2,913 additions and 67 deletions.
2 changes: 0 additions & 2 deletions .github/actions/determine-target-image-tags/action
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ echo "MAJOR_VERSION=${MAJOR_VERSION}"
test -n "${VERSION}" || ( echo "VERSION is not specified" && false )
test -n "${MAJOR_VERSION}" || ( echo "MAJOR_VERSION is not specified" && false )

git fetch --tags

GLOBAL_LATEST_VERSION="$(git-semver latest --include-pre-releases)"
echo "GLOBAL_LATEST_VERSION=${GLOBAL_LATEST_VERSION}"

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/bump-version.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
name: Bump Version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: PSanetra/git-semver-actions/next@v1
id: next_version
- uses: PSanetra/git-semver-actions/latest@v1
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
name: On Tag pushed
name: Test and Push Images on new Tag

on:
push:
tags:
- v*

jobs:
test:
push_images:
name: Test and Push Images
runs-on: ubuntu-latest
strategy:
matrix:
# "{0}" will be replaced by the latest pushed nginx version
nginx: [ "mainline", "stable", "{0}" ]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
ref: ${{ github.ref }}
fetch-depth: 0
fetch-tags: true

- name: Get latest nginx tag
id: latest_nginx_tag
uses: ./.github/actions/latest-docker-repository-version
Expand All @@ -39,7 +43,7 @@ jobs:
git-ref: "${{ github.ref }}"
nginx-tag: "${{ steps.target_nginx_tag.outputs.tag }}"
matrix-nginx: "${{ matrix.nginx }}"
docker-repository: "${{ secrets.DOCKER_REPOSITORY }}"
docker-repository: "${{ vars.DOCKER_REPOSITORY }}"
- uses: docker/setup-qemu-action@v2
name: Set up QEMU
- uses: docker/setup-buildx-action@v2
Expand Down
41 changes: 41 additions & 0 deletions .github/workflows/publish-helm-chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Test and Push Helm Chart on new Tag

on:
push:
tags:
- 'v*'

jobs:
test_and_push_helm_chart:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Get Latest Semantic Version
id: get_latest_version
uses: PSanetra/git-semver-actions/latest@v1

- name: Set up Helm
uses: azure/setup-helm@v3

- name: Install Helm Unittest Plugin
run: helm plugin install https://github.com/helm-unittest/helm-unittest

- name: Run Helm Lint
run: helm lint chart --strict

- name: Run Helm Unit Tests
run: helm unittest chart --strict

- name: Package Helm Chart
run: helm package chart -u --version ${{ steps.get_latest_version.outputs.version }} --destination .

- name: Install yq
run: sudo snap install yq

- name: Push Helm Chart to OCI Registry
run: |
echo '${{ secrets.DOCKER_PASSWORD }}' | helm registry login ${{ vars.HELM_REGISTRY }} --username '${{ secrets.DOCKER_USERNAME }}' --password-stdin
helm push $(yq -r '.name' ./chart/Chart.yaml | tr -d '\n')-${{ steps.get_latest_version.outputs.version }}.tgz ${{ vars.HELM_REPOSITORY }}
12 changes: 6 additions & 6 deletions .github/workflows/update.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Update Docker Images
name: Update Images
on:
push:
branches:
Expand All @@ -17,7 +17,7 @@ jobs:
# "{0}" will be replaced by the latest pushed nginx version
nginx: [ "mainline", "stable", "{0}" ]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Get latest nginx tag
id: latest_nginx_tag
uses: ./.github/actions/latest-docker-repository-version
Expand All @@ -33,12 +33,12 @@ jobs:
id: target_image_name
run: |
IMAGE_TAG="${{ matrix.spa_server_major_version }}-nginx-${{ steps.target_nginx_tag.outputs.tag }}"
IMAGE_NAME="${{ secrets.DOCKER_REPOSITORY }}:${IMAGE_TAG}"
IMAGE_NAME="${{ vars.DOCKER_REPOSITORY }}:${IMAGE_TAG}"
echo "::set-output name=image_name::${IMAGE_NAME}"
echo "IMAGE_NAME=${IMAGE_NAME}"
TAGS="${{ secrets.DOCKER_REPOSITORY }}:${IMAGE_TAG}"
TAGS="${{ vars.DOCKER_REPOSITORY }}:${IMAGE_TAG}"
if [ "latest" = "${{ matrix.spa_server_major_version }}" ] && [ "stable" = "${{ matrix.nginx }}" ]; then
TAGS="${TAGS},${{ secrets.DOCKER_REPOSITORY }}:${{ matrix.spa_server_major_version }},${{ secrets.DOCKER_REPOSITORY }}:latest"
TAGS="${TAGS},${{ vars.DOCKER_REPOSITORY }}:${{ matrix.spa_server_major_version }},${{ vars.DOCKER_REPOSITORY }}:latest"
fi
echo "::set-output name=tags::${TAGS}"
echo "TAGS=${TAGS}"
Expand All @@ -58,7 +58,7 @@ jobs:
run: sudo chmod -R ugo+rwX .
if: steps.check_if_update_is_necessary.outputs.needs_update == 'true'
- name: Checkout latest SPA-Server version for major version ${{ matrix.spa_server_major_version }}
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
ref: "v${{ steps.latest_release.outputs.version }}"
if: steps.check_if_update_is_necessary.outputs.needs_update == 'true'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/config/.out
/config_tests/.tmp
/tests/target
*.tgz
110 changes: 56 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Single Page Application Server

![Update Docker Images](https://github.com/codecentric/single-page-application-server/workflows/Update%20Docker%20Images/badge.svg)

This image can be used as a base image for single page applications. It is itself based on Nginx.
This container image provides a base for serving Single Page Applications (SPAs), leveraging Nginx as its web server.

For a fitting Helm Chart see [chart/README.md](https://github.com/codecentric/single-page-application-server/blob/master/chart/README.md).

## Tags

The following tags will be updated automatically with the latest nginx base image on a weekly basis:
The following tags are updated automatically on a weekly basis with the latest Nginx base image:

* `latest` (alias for `1-nginx-stable-alpine`)
* `1` (alias for `1-nginx-stable-alpine`)
Expand All @@ -14,54 +17,73 @@ The following tags will be updated automatically with the latest nginx base imag
* `latest-nginx-mainline-alpine` (alias for `1-nginx-mainline-alpine`)
* `1-nginx-mainline-alpine`

There will also be tags for specific versions.
Additional tags for specific Nginx versions are also available.

## Examples

Examples for usage with Angular and React are located in the `examples` directory.

## General Features

* Any non-existing routes return the root `index.html`
* Does NOT apply to resource routes with filename extensions such as: `js | css | ico | pdf | flv | jpg | jpeg | png | gif | swf`
* Configure application dynamically at container startup
* Configure base element
* Support environment specific configuration depending on the requested host (port and domain)
* Hashed resources are cached indefinitely by the browser without revalidating
* Resource name needs to look like `my-script.3f8a240b.js` or `my-script.3f8a240b.chunk.js`
* Hash needs to consist of at least 8 characters
* Applies to resources with filename extensions such as: `js | css | ico | pdf | flv | jpg | jpeg | png | gif | swf`
* HTTP 2 is enabled by default for HTTPS connections
- **SPA Routes Handling**: Routes not matching static files will serve `index.html`, with exceptions for resources like `.js`, `.css`, etc.
- **Dynamic Configuration**: Configure applications at container startup.
- **Environment-Specific Config**: Customize settings based on port and domain.
- **Resource Caching**: Hashed resources are cached indefinitely. Resources must include a hash of at least 8 characters.
- **HTTP/2**: Enabled by default for HTTPS connections.
- **Helm Chart**: A general [Helm chart](https://github.com/codecentric/single-page-application-server/blob/master/chart/README.md) is available for applications using this image.

## Security Features

* Restrictive Content Security Policy by default
* Server API endpoints can be whitelisted automatically
* Referrer is disabled by default
* Content Type Sniffing is disabled by default
* HTTPS is enforced via HSTS by default if enabled
* HTTPS uses [recommended OWASP protocols and cipher suites](https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html)
* Container runs as non-root user
* Nevertheless the server can still bind to port 80 and 443
* Serving source map files (*.js.map, *.css.map) is disabled by default
- **Content Security Policy**: Restrictive by default, with automatic whitelisting for server API endpoints.
- **Referrer Policy**: Disabled by default to prevent leakage.
- **Content Type Sniffing**: Disabled by default.
- **HTTPS**: Enforced via HSTS if enabled; uses [recommended OWASP protocols and cipher suites.](https://cheatsheetseries.owasp.org/cheatsheets/TLS_Cipher_String_Cheat_Sheet.html)
- **Non-Root User**: The container runs as a non-root user but can bind to ports 80 and 443.
- **Source Maps**: Disabled by default.
- **Read-only root filesystem**: [Supported at container runtime](#read-only-root-filesystem-support)

## Configuration

### App Directory

Copy your SPA resources to `/app/`. All resources in this directory will be served by the Nginx server.
Place your SPA resources in `/app/`. All files in this directory will be served by Nginx.

### YAML Configuration

The application container is configured via YAML files at startup time.
Configure the application through YAML files at startup:

If you need to configure some default settings for your application image, you can add a default configuration file to `/config/default.yaml`.
1. **Default Configuration**: Add a default configuration file to `/config/default.yaml`. Usually added during `docker build`.
2. **Runtime Configuration**: Mount a runtime configuration file at `/config/config.yaml`. This file will override default settings.

To configure settings at runtime, you can mount your runtime configuration file at `/config/config.yaml`. Every specified setting in this file will override the default setting.
#### Example Configuration

It is also possible to merge multiple configuration files by specifying the `CONFIG_FILES` environment variable like `CONFIG_FILES="file:///config/config1.yaml|file:///config/config2.yaml"`. The configuration options, specified in the first configuration file in that variable, will have priority over the options in later declared files.
```yaml
default:
spa_config:
appTitle: "My Application"
endpoints:
api: "https://api.example.com"
```
You can also define host-specific configurations:
```yaml
default:
spa_config:
appTitle: "My Default Title"
endpoints:
api: "https://api.example.com"
special_host:
server_names:
- "special.example.com"
spa_config:
appTitle: "My Domain-specific Title"
```
#### Configuration Reference
The following configuration shows the default values of this base image for every available setting:
```yaml
default:
# Specifies to which host names this configuration should apply.
Expand Down Expand Up @@ -132,40 +154,20 @@ default:
style-src: "'self'"
```
Aside to the `default` configuration block, you can also define other blocks, which might define configuration settings for special hosts. The non-default blocks will inherit the configured settings of the default-block if they are not explicitly redeclared.

Example:
```yaml
default:
spa_config:
appTitle: "My Default Application"
endpoints:
globalApi: "https://api.example.com"
special_host:
server_names:
- "special.example.com"
spa_config:
appTitle: "My Special Application"
```

With this configuration the application would have the app title "My Special Application", when it is accessed via the host `special.example.com`, while the endpoints would stay the same in every instance of the application.

## Read-only Root Filesystem Support
It is recommended to use a read-only root filesystem when running containers. However, the following directories must remain writable when using this base image:
For security, use a read-only root filesystem. Ensure the following directories are writable:
* `/config/.out`
* This base image generates files in this directory at startup.
* `/tmp`
* Nginx uses this directory to manage cached files and the nginx.pid file. For more information, see [nginxinc/docker-nginx-unprivileged#troubleshooting-tips](https://github.com/nginxinc/docker-nginx-unprivileged/tree/af6e325d35e6833af9cdda8493866b88649e8aaf?tab=readme-ov-file#troubleshooting-tips).
* `/config/.out`: Used for file generation.
* `/tmp`: Used by Nginx for cached files and `nginx.pid`.

It is possible to mount these directories as writable volumes. When using Kubernetes, one solution is to mount `emptyDir` volumes at these mount points.
When using Kubernetes, consider mounting these directories as writable volumes with `emptyDir`.

## Development

Configuration files are dynamically generated via [gomplate templates](https://docs.gomplate.ca/).

Tests are written in Java using [Testcontainers](https://www.testcontainers.org/).
* **Configuration Generation**: Uses [gomplate templates](https://docs.gomplate.ca/).
* **Image Tests**: Written in Java using [Testcontainers](https://www.testcontainers.org/).
* **Helm Chart Tests**: Uses the [helm-unittest](https://github.com/helm-unittest/helm-unittest) Helm plugin.

## License

Expand Down
26 changes: 26 additions & 0 deletions chart/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
# Testing
ci
tests
10 changes: 10 additions & 0 deletions chart/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v2
name: single-page-application-server-chart
description: Helm chart for images based on codecentric/single-page-application-server
type: application
version: 0.0.0
maintainers:
- name: Philip Sanetra
email: [email protected]
sources:
- "https://github.com/codecentric/single-page-application-server"
Loading

0 comments on commit 73aa4bd

Please sign in to comment.