tarampampam / error-pages

🚧 Pretty server's error pages in the docker image & git repository (for traefik, k8s, nginx and so on)
https://tarampampam.github.io/error-pages/
MIT License
802 stars 78 forks source link

Support Docker Volumes for Configurable Error Pages #285

Closed deffcolony closed 3 weeks ago

deffcolony commented 1 month ago

Is there an existing issue for this?

Describe the problem to be solved

Currently, configuring error pages in Docker containers requires manual adjustments to the entrypoint script and ensuring proper permissions for mounted directories. Users need a streamlined way to mount volumes for error page templates, HTML, and configuration files, allowing easy customization and configuration without modifying the container's filesystem or rebuilding the image. Additionally, users should have shell access with docker exec to manage files if needed

Suggest a solution

Implement support for Docker volumes in the docker-entrypoint.sh script and Dockerfile to allow users to mount local directories to the container. This can be achieved by modifying the entrypoint script to handle symbolic links and permissions, and updating the Dockerfile to ensure proper setup. The following changes are suggested:

  1. Dockerfile Adjustments:
  1. Entrypoint Script:
  1. Shell Access:
    • Ensure users have shell access using the alpine image in dockerfile with docker exec to manage files if needed.
    • Maintain appropriate permissions so users can read and write to mounted volumes without requiring elevated privileges.

Additional context

The following is a proposed Dockerfile and docker-entrypoint.sh script to support volume mounting and to support shell access:

dockerfile changes

# use alpine instead of scratch
FROM alpine:latest AS runtime

# Copy the entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/

# Make the script executable
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# Install recommended packages
RUN apk add --no-cache bash rsync nano

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

dockerfile full output

# syntax=docker/dockerfile:1

# this stage is used to build the application
FROM docker.io/library/golang:1.22-bookworm AS builder

COPY ./go.* /src/

WORKDIR /src

# burn the modules cache
RUN go mod download

# this stage is used to compile the application
FROM builder AS compiler

# can be passed with any prefix (like `v1.2.3@GITHASH`), e.g.: `docker build --build-arg "APP_VERSION=v1.2.3@GITHASH" .`
ARG APP_VERSION="undefined@docker"

WORKDIR /src

COPY . .

# arguments to pass on each go tool link invocation
ENV LDFLAGS="-s -w -X gh.tarampamp.am/error-pages/internal/version.version=$APP_VERSION"

# build the application
RUN set -x \
    && CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./error-pages ./cmd/error-pages/ \
    && ./error-pages --version \
    && ./error-pages -h

WORKDIR /tmp/rootfs

# prepare rootfs for runtime
RUN set -x \
    && mkdir -p \
        ./etc \
        ./bin \
        ./opt/html \
    && echo 'appuser:x:10001:10001::/nonexistent:/sbin/nologin' > ./etc/passwd \
    && echo 'appuser:x:10001:' > ./etc/group \
    && mv /src/error-pages ./bin/error-pages \
    && mv /src/templates ./opt/templates \
    && rm ./opt/templates/*.md \
    && mv /src/error-pages.yml ./opt/error-pages.yml

WORKDIR /tmp/rootfs/opt

# generate static error pages (for usage inside another docker images, for example)
RUN set -x \
    && ./../bin/error-pages --verbose build --config-file ./error-pages.yml --index ./html \
    && ls -l ./html

# use alpine instead of scratch
FROM alpine:latest AS runtime

ARG APP_VERSION="undefined@docker"

LABEL \
    # Docs: <https://github.com/opencontainers/image-spec/blob/master/annotations.md>
    org.opencontainers.image.title="error-pages" \
    org.opencontainers.image.description="Static server error pages in the docker image" \
    org.opencontainers.image.url="https://github.com/tarampampam/error-pages" \
    org.opencontainers.image.source="https://github.com/tarampampam/error-pages" \
    org.opencontainers.image.vendor="tarampampam" \
    org.opencontainers.version="$APP_VERSION" \
    org.opencontainers.image.licenses="MIT"

# Install recommended packages
RUN apk add --no-cache bash rsync nano

# Import from compiler build stage
COPY --from=compiler /tmp/rootfs /

# Copy the entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/

# Make the script executable
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

# Use an unprivileged user
USER 10001:10001

WORKDIR /opt

# Set environment variables
ENV LISTEN_PORT="8080" \
    TEMPLATE_NAME="ghost" \
    DEFAULT_ERROR_PAGE="404" \
    DEFAULT_HTTP_CODE="404" \
    SHOW_DETAILS="false" \
    DISABLE_L10N="false" \
    READ_BUFFER_SIZE="2048"

# Healthcheck
HEALTHCHECK --interval=7s --timeout=2s CMD ["/bin/error-pages", "--log-json", "healthcheck"]

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["--log-json", "serve"]

docker-entrypoint.sh

#!/bin/sh

# ANSI Escape Code for Colors
reset="\033[0m"
white_fg_strong="\033[90m"
red_fg_strong="\033[91m"
green_fg_strong="\033[92m"
yellow_fg_strong="\033[93m"
blue_fg_strong="\033[94m"
magenta_fg_strong="\033[95m"
cyan_fg_strong="\033[96m"

# Normal Background Colors
red_bg="\033[41m"
blue_bg="\033[44m"
yellow_bg="\033[43m"

# Function to log messages with timestamps and colors
log_message() {
    # This is only time
    current_time=$(date +'%H:%M:%S')
    # This is with date and time
    # current_time=$(date +'%Y-%m-%d %H:%M:%S')
    case "$1" in
        "INFO")
            echo -e "${blue_bg}[$current_time]${reset} ${blue_fg_strong}[INFO]${reset} $2"
            ;;
        "WARN")
            echo -e "${yellow_bg}[$current_time]${reset} ${yellow_fg_strong}[WARN]${reset} $2"
            ;;
        "ERROR")
            echo -e "${red_bg}[$current_time]${reset} ${red_fg_strong}[ERROR]${reset} $2"
            ;;
        *)
            echo -e "${blue_bg}[$current_time]${reset} ${blue_fg_strong}[DEBUG]${reset} $2"
            ;;
    esac
}

set -e

# Default to appuser (UID 10001), so old installations won't break
export PUID=${PUID:-10001}
export PGID=${PGID:-10001}

# Create symbolic links if necessary
if [ ! -d "/opt/templates" ]; then
    ln -s /config/templates /opt/templates
    log_message "INFO" "Created symbolic link: /config/templates -> /opt/templates"
fi

if [ ! -d "/opt/html" ]; then
    ln -s /config/html /opt/html
    log_message "INFO" "Created symbolic link: /config/html -> /opt/html"
fi

if [ ! -f "/opt/error-pages.yml" ]; then
    ln -s /config/error-pages.yml /opt/error-pages.yml
    log_message "INFO" "Created symbolic link: /config/error-pages.yml -> /opt/error-pages.yml"
fi

export ERRORPAGES_BUILDTIME=$(date +%s)

# Set privileges for /opt and /config but only if pid 1 user is root and we are dropping privileges.
# If container is run as an unprivileged user, it means owner already handled ownership setup on their own.
# Running chown in that case (as non-root) will cause error
if [ "$(id -u)" = "0" ] && [ "${PUID}" != "0" ]; then
    chown -R ${PUID}:${PGID} /opt
    chown -R ${PUID}:${PGID} /config
    chown -R 10001:10001 /config/templates
    chown -R 10001:10001 /config/html
    log_message "INFO" "Changed ownership of /opt and /config to ${PUID}:${PGID}"
    log_message "INFO" "Changed ownership of /config/templates and /config/html to 10001:10001"
fi

# Drop privileges (when asked to) if root, otherwise run as current user
if [ "$(id -u)" = "0" ] && [ "${PUID}" != "0" ]; then
    log_message "INFO" "Starting error-pages as user ${PUID}:${PGID}"
    su-exec ${PUID}:${PGID} /bin/error-pages "$@"
else
    log_message "WARN" "No privileges to drop. Running error-pages as current user."
    exec /bin/error-pages "$@"
fi

Make sure to add docker-entrypoint.sh to the .dockerignore file

.dockerignore

## Ignore everything
*

## Except the following files and directories
!/docker-entrypoint.sh
!/cmd
!/internal
!/templates
!/error-pages.yml
!/go.*

By following this approach, users can mount volumes with the docker compose file:

docker-compose.yml

services:
  error-pages:
    image: error-pages:latest
    container_name: error-pages
    environment:
      PGID: 1000
      PUID: 1000
      TEMPLATE_NAME: connection # set the error pages template
    volumes:
      - ./error-pages/data/templates:/config/templates
      - ./error-pages/data/html:/config/html
      - ./error-pages/data/config/error-pages.yml:/config/error-pages.yml:rw
    ports:
      - "8176:8080"
    restart: unless-stopped
tarampampam commented 1 month ago

Hello there,

Thank you for providing a detailed explanation of your issue! Currently, I'm working on a new major version of error-pages. Once the work is complete, I'll ping you here, and I hope you'll be pleasantly surprised by the new approach to handling custom templates 😉

Its4Nik commented 1 month ago

Hello there,

Thank you for providing a detailed explanation of your issue! Currently, I'm working on a new major version of error-pages. Once the work is complete, I'll ping you here, and I hope you'll be pleasantly surprised by the new approach to handling custom templates 😉

Does this mean that this was kinda useless of me? https://github.com/Its4Nik/NGX-Error

I added a custom github action that builds every theme there is and pushes it to ghcr.io

tarampampam commented 1 month ago

Does this mean that this was kinda useless of me?

¯\ (ツ)

Even if you only found your solution useful - it makes sense, right?

Its4Nik commented 1 month ago

Does this mean that this was kinda useless of me?

¯\ (ツ)

Even if you only found your solution useful - it makes sense, right?

Yeah ig :)

tarampampam commented 3 weeks ago

I could try to answer your question at https://github.com/tarampampam/error-pages/issues/288#issuecomment-2208654728 here

How can I configure my Traefik and replicate this into the new V3 error-pages docker container setup so that the static assets for the error pages are served correctly without being 403 forbidden? Any insights or suggestions would be greatly appreciated.

The error-pages project was designed to operate with error pages as single HTML pages, without loading additional resources from additional css or js files. This constraint simplifies deployment and usage, avoiding the challenges you've encountered.

The compose.yml file in this repository is intended only for local development and testing. Please do not refer to it when looking for solutions to use the project in your actual use case.

Is it possible for you to embed the content of every css and/or js file into a single HTML file, including images and everything you need for your error pages? If so, this might solve your problem (if I understood it correctly). You can then mount your template and specify the error-pages to use it, as mentioned in the readme file.