Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ DB_PASSWORD=YOUR_STRONG_DB_PASSWORD_HERE
# ============================================
# WEBHOOK AUTHENTICATION
# ============================================
# BasicAuth credentials for /webhook endpoint
# These will be automatically hashed and configured in Traefik during deployment
WEBHOOK_USERNAME=your-email@example.com
WEBHOOK_PASSWORD=your-secure-password
# BasicAuth credentials for /webhook endpoint (format: user:hash)
# Generate hash with: htpasswd -nb user password
# Default if not set: admin:admin
# WEBHOOK_BASIC_AUTH=

# ============================================
# CACHET Admin user
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/build-container.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Build and Publish Container

on:
push:
branches: [ main ]
tags:
- '*'
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write

strategy:
matrix:
image:
- name: cachet-status-page
dockerfile: cachet-configuration-files/Dockerfile
path: cachet-configuration-files
- name: middleware-status-page
path: middleware
dockerfile: middleware/Dockerfile

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

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Determine image tags
id: tags
run: |
SHORT_SHA=${GITHUB_SHA::8}
IMAGE=ghcr.io/${GITHUB_REPOSITORY_OWNER}/${{ matrix.image.name }}
# Default tag is sha
echo "tags=${IMAGE}:sha-${SHORT_SHA}" >> $GITHUB_OUTPUT

if [[ "$GITHUB_REF" == refs/tags/* ]]; then
TAG_NAME=${GITHUB_REF#refs/tags/}
echo "tags=${IMAGE}:sha-${SHORT_SHA},${IMAGE}:${TAG_NAME}" >> $GITHUB_OUTPUT
elif [[ "$GITHUB_REF" == refs/heads/main ]]; then
echo "tags=${IMAGE}:sha-${SHORT_SHA},${IMAGE}:latest" >> $GITHUB_OUTPUT
else
# sanitize branch name: replace / with - and remove non-alphanum ._- characters
BRANCH=${GITHUB_REF#refs/heads/}
SAFE_BRANCH=$(echo "$BRANCH" | sed 's#[^A-Za-z0-9._-]#-#g')
echo "tags=${IMAGE}:sha-${SHORT_SHA},${IMAGE}:${SAFE_BRANCH}" >> $GITHUB_OUTPUT
fi

- name: Build and push
uses: docker/build-push-action@v4
with:
context: ${{ matrix.image.path }}
file: ${{ matrix.image.dockerfile }}
platforms: linux/amd64
push: true
tags: ${{ steps.tags.outputs.tags }}
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ nano .env
- `APP_KEY`
- `CACHET_API_TOKEN`

**Authentication:**
The webhook endpoint is protected by Basic Auth. You can configure the credentials by setting the `WEBHOOK_BASIC_AUTH` environment variable in `.env`.
The format is `user:hash`. You can generate the hash using `htpasswd -nb user password` or an online generator (BCrypt, MD5, SHA1).
If not set, the default credentials are `admin:admin`.

> **Note for rootless Podman and privileged ports (80/443):**
>
> By default, non-root users cannot bind to ports below 1024 (such as 80 and 443). If you want to expose Traefik or other services directly on these ports in rootless mode, you must configure the following kernel parameter on your host system:
Expand Down
197 changes: 87 additions & 110 deletions cachet-configuration-files/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,92 +1,12 @@
# Multi-stage build for production Cachet deployment
ARG PHP_VERSION
# Single-stage build for production Cachet deployment
ARG PHP_VERSION=8.3
ARG CACHET_COMMIT=207a6d5a7c309fcdc4656a074869f0afa06d142c

# ============================================
# Stage 1: Composer dependencies
# ============================================
FROM docker.io/library/php:${PHP_VERSION}-cli-alpine AS composer

# Allow use of all user's SSH keys/config for private repository authentication
# Temporarily copy the entire .ssh directory
COPY --chown=root:root .ssh /root/.ssh
RUN chmod 700 /root/.ssh && chmod 600 /root/.ssh/*

ENV GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=yes"

# Install composer, build tools, and required extensions
RUN apk add --no-cache \
composer \
icu-dev \
libxml2-dev \
autoconf \
g++ \
make \
git \
openssh \
&& docker-php-ext-install \
intl \
dom \
xml \
simplexml \
session \
&& php -m | grep -i intl \
&& php -m | grep -i dom \
&& php -m | grep -i simplexml \
&& php -m | grep -i tokenizer \
&& php -m | grep -i session

WORKDIR /app

COPY ./cachet/composer.json ./cachet/composer.lock ./

# Install dependencies (including dev for now - Pail is required by config/app.php)
# Note: We ignore platform requirements for extensions that are:
# - Bundled in PHP 8.3 (fileinfo, session, tokenizer, simplexml, xmlreader, xmlwriter)
# - Installed but not detected correctly by composer (intl, dom, xml)
RUN composer install \
--no-interaction \
--no-progress \
--no-scripts \
--prefer-dist \
--optimize-autoloader \
--ignore-platform-req=php+ \
--ignore-platform-req=ext-simplexml \
--ignore-platform-req=ext-fileinfo \
--ignore-platform-req=ext-session \
--ignore-platform-req=ext-tokenizer \
--ignore-platform-req=ext-xmlreader \
--ignore-platform-req=ext-xmlwriter \
--ignore-platform-req=ext-intl \
--ignore-platform-req=ext-dom \
--ignore-platform-req=ext-xml

# Update cachethq/core dependency
# RUN composer update cachethq/core
# --no-interaction \
# --no-dev \
# --prefer-dist \
# --no-progress \
# --no-scripts \
# --ignore-platform-req=ext-simplexml \
# --ignore-platform-req=ext-fileinfo \
# --ignore-platform-req=ext-session \
# --ignore-platform-req=ext-tokenizer \
# --ignore-platform-req=ext-xmlreader \
# --ignore-platform-req=ext-xmlwriter \
# --ignore-platform-req=ext-intl \
# --ignore-platform-req=ext-dom \
# --ignore-platform-req=ext-xml

# Remove SSH keys after use for security
RUN rm -rf /root/.ssh

# ============================================
# Stage 3: Production PHP runtime
# ============================================
FROM docker.io/library/php:${PHP_VERSION}-fpm-alpine

# Install system dependencies
# Install system dependencies and composer
RUN apk add --no-cache \
composer \
nginx \
supervisor \
postgresql-dev \
Expand All @@ -97,10 +17,24 @@ RUN apk add --no-cache \
oniguruma-dev \
icu-dev \
libxml2-dev \
libpq \
icu-libs \
libjpeg-turbo \
libpng \
freetype \
libzip \
oniguruma \
libxml2 \
curl \
bash \
git \
autoconf \
g++ \
make \
ca-certificates \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo \
pdo_pgsql \
pgsql \
zip \
Expand All @@ -111,41 +45,65 @@ RUN apk add --no-cache \
xml \
simplexml \
session \
&& rm -rf /var/cache/apk/*

# Install PHP extensions for production
RUN docker-php-ext-enable opcache
&& docker-php-ext-enable opcache pdo pdo_pgsql

# Configure PHP for production (relative to podman-setup)
COPY ./cachet/docker/php/php.ini /usr/local/etc/php/conf.d/cachet.ini
COPY ./cachet/docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini
COPY ./docker/php/php.ini /usr/local/etc/php/conf.d/cachet.ini
COPY ./docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Configure Nginx
COPY ./cachet/docker/nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./cachet/docker/nginx/default.conf /etc/nginx/http.d/default.conf
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./docker/nginx/default.conf /etc/nginx/http.d/default.conf

# Configure Supervisor
COPY ./cachet/docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Create application user
RUN addgroup -g 1000 -S cachet && \
adduser -u 1000 -S cachet -G cachet
COPY ./docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Set working directory
WORKDIR /var/www/html

# Copy application files (relative to podman-setup)
COPY --chown=cachet:cachet ./cachet/. .
# Clone Cachet repository
RUN git clone https://github.com/cachethq/cachet.git /var/www/html/ \
&& git --git-dir /var/www/html/.git checkout ${CACHET_COMMIT}

# Copy local configuration files into the cloned Cachet repository
COPY --chown=root:root ./proxy/TrustProxies.php /var/www/html/app/Http/Middleware/TrustProxies.php

# Copy composer dependencies from builder
# Note: With symlink=false in composer.json, core is copied into vendor/cachethq/core
COPY --from=composer --chown=cachet:cachet /app/vendor ./vendor
# Copy AdminSeeder to enable admin user creation via environment variables
COPY ./token/AdminSeeder.php /var/www/html/database/seeders/AdminSeeder.php

# Install composer dependencies (no dev)
# Note: We ignore platform requirements for extensions that are:
# - Bundled in PHP 8.3 (fileinfo, session, tokenizer, simplexml, xmlreader, xmlwriter)
# - Installed but not detected correctly by composer (intl, dom, xml)
RUN cd /var/www/html && composer install --no-dev \
--no-interaction \
--no-progress \
--no-scripts \
--prefer-dist \
--optimize-autoloader \
--ignore-platform-req=php+ \
--ignore-platform-req=ext-simplexml \
--ignore-platform-req=ext-fileinfo \
--ignore-platform-req=ext-session \
--ignore-platform-req=ext-tokenizer \
--ignore-platform-req=ext-xmlreader \
--ignore-platform-req=ext-xmlwriter \
--ignore-platform-req=ext-intl \
--ignore-platform-req=ext-dom \
--ignore-platform-req=ext-xml

# Clear composer cache to save space
RUN composer clear-cache

# Remove .git folder to save space
RUN rm -rf .git

# Publish Cachet assets (config, views, and public assets)
RUN php artisan vendor:publish --tag=cachet --force

# Copy custom images (from build context) into the published vendor folder (overwrite default images)
COPY ./cachet-configuration-files/images/ /var/www/html/public/vendor/cachethq/cachet/
# build context is cachet-configuration-files, so images are in ./images/
COPY ./images/ /var/www/html/public/vendor/cachethq/cachet/

# Publish Livewire config and views
RUN php artisan livewire:publish
Expand All @@ -156,20 +114,39 @@ RUN php artisan filament:assets
# Create storage symlink for public uploads (banner, ecc.)
RUN php artisan storage:link

# Ensure migrations for database-backed drivers exist
RUN php artisan cache:table || true
RUN php artisan session:table || true
RUN php artisan queue:table || true

# Fix permissions AFTER artisan commands - PHP-FPM runs as www-data
# Important: artisan commands may create files as cachet user, so we fix ownership
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 775 /var/www/html/storage \
RUN chmod -R 775 /var/www/html/storage \
&& chmod -R 775 /var/www/html/bootstrap/cache

# Create necessary directories
RUN mkdir -p /var/www/html/storage/framework/{sessions,views,cache} \
&& mkdir -p /var/www/html/storage/logs \
&& mkdir -p /var/log/supervisor \
&& mkdir -p /var/run/supervisor \
&& chown -R www-data:www-data /var/www/html/storage \
&& chown -R www-data:www-data /var/www/html/bootstrap/cache
&& chown -R www-data:www-data /var/www/html

# Clean up build dependencies
RUN apk del -r --no-cache \
postgresql-dev \
libzip-dev \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
oniguruma-dev \
icu-dev \
libxml2-dev \
autoconf \
g++ \
make \
git \
ca-certificates \
&& rm -rf /var/cache/apk/*

# Expose port 80 for Nginx
EXPOSE 80

Expand All @@ -178,4 +155,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD php artisan cachet:health || exit 1

# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
CMD ["sh", "-c", "until php artisan migrate --force; do echo 'Waiting for database...'; sleep 2; done && /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf"]
13 changes: 9 additions & 4 deletions cachet-configuration-files/docker/supervisor/supervisord.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
pidfile=/var/run/supervisord.pid

[program:php-fpm]
Expand Down Expand Up @@ -29,8 +32,10 @@ autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=cachet
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/www/html/storage/logs/queue-worker.log
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stopwaitsecs=3600
Loading