Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Docker Volumes for Configurable Error Pages #285

Closed
1 task done
deffcolony opened this issue Jun 19, 2024 · 5 comments
Closed
1 task done

Support Docker Volumes for Configurable Error Pages #285

deffcolony opened this issue Jun 19, 2024 · 5 comments
Assignees
Labels
type:feature_request Feature request
Milestone

Comments

@deffcolony
Copy link

deffcolony commented Jun 19, 2024

Is there an existing issue for this?

  • I have searched the existing issues

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:
  • Ensure directories like /config/templates, /config/html, and /config/error-pages.yml are writable by the container user.
  • Install necessary dependencies and set up the environment for running the error pages application.
  1. Entrypoint Script:
  • Create symbolic links from /config to /opt if the directories or files do not already exist.
  • Set appropriate permissions for the /config directories and files to ensure they are writable by the container's user.
  • Log actions and potential issues with detailed and colored messages for easier debugging and monitoring.
  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
Copy link
Owner

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
Copy link

Its4Nik commented Jun 27, 2024

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
Copy link
Owner

tarampampam commented Jun 27, 2024

Does this mean that this was kinda useless of me?

¯\_ (ツ) _/¯

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

@Its4Nik
Copy link

Its4Nik commented Jun 28, 2024

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 tarampampam added this to the v3 milestone Jul 3, 2024
@tarampampam tarampampam mentioned this issue Jul 3, 2024
Merged
4 tasks
@tarampampam
Copy link
Owner

I could try to answer your question at #288 (comment) 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:feature_request Feature request
Projects
None yet
Development

No branches or pull requests

3 participants