redaction (#1)

Add the redacted source file for demo purposes

Reviewed-on: https://source.michaeldileo.org/michael_dileo/Keybard-Vagabond-Demo/pulls/1
Co-authored-by: Michael DiLeo <michael_dileo@proton.me>
Co-committed-by: Michael DiLeo <michael_dileo@proton.me>
This commit was merged in pull request #1.
This commit is contained in:
2025-12-24 13:40:47 +00:00
committed by michael_dileo
parent 612235d52b
commit 7327d77dcd
333 changed files with 39286 additions and 1 deletions

291
build/pixelfed/README.md Normal file
View File

@@ -0,0 +1,291 @@
# Pixelfed Kubernetes-Optimized Containers
This directory contains **separate, optimized Docker containers** for Pixelfed v0.12.6 designed specifically for Kubernetes deployment with your infrastructure.
## 🏗️ **Architecture Overview**
### **Three-Container Design**
1. **`pixelfed-base`** - Shared foundation image with all Pixelfed dependencies
2. **`pixelfed-web`** - Web server handling HTTP requests (Nginx + PHP-FPM)
3. **`pixelfed-worker`** - Background job processing (Laravel Horizon + Scheduler)
### **Why Separate Containers?**
**Independent Scaling**: Scale web and workers separately based on load
**Better Resource Management**: Optimize CPU/memory for each workload type
**Enhanced Monitoring**: Separate metrics for web performance vs queue processing
**Fault Isolation**: Web issues don't affect background processing and vice versa
**Rolling Updates**: Update web and workers independently
**Kubernetes Native**: Works perfectly with HPA, resource limits, and service mesh
## 🚀 **Quick Start**
### **Build All Containers**
```bash
# From the build/ directory
./build-all.sh
```
This will:
1. Build the base image with all Pixelfed dependencies
2. Build the web container with Nginx + PHP-FPM
3. Build the worker container with Horizon + Scheduler
4. Push to your Harbor registry: `<YOUR_REGISTRY_URL>`
### **Individual Container Builds**
```bash
# Build just web container
cd pixelfed-web && docker build --platform linux/arm64 \
-t <YOUR_REGISTRY_URL>/pixelfed/web:v6 .
# Build just worker container
cd pixelfed-worker && docker build --platform linux/arm64 \
-t <YOUR_REGISTRY_URL>/pixelfed/worker:v0.12.6 .
```
## 📦 **Container Details**
### **pixelfed-web** - Web Server Container
**Purpose**: Handle HTTP requests, API calls, file uploads
**Components**:
- Nginx (optimized with rate limiting, gzip, security headers)
- PHP-FPM (tuned for web workload with connection pooling)
- Static asset serving with CDN fallback
**Resources**: Optimized for HTTP response times
**Health Check**: `curl -f http://localhost:80/api/v1/instance`
**Scaling**: Based on HTTP traffic, CPU usage
### **pixelfed-worker** - Background Job Container
**Purpose**: Process federation, image optimization, emails, scheduled tasks
**Components**:
- Laravel Horizon (queue management with Redis)
- Laravel Scheduler (cron-like task scheduling)
- Optional high-priority worker for urgent tasks
**Resources**: Optimized for background processing throughput
**Health Check**: `php artisan horizon:status`
**Scaling**: Based on queue depth, memory usage
## ⚙️ **Configuration**
### **Environment Variables**
Both containers share the same configuration:
#### **Required**
```bash
APP_DOMAIN=pixelfed.keyboardvagabond.com
DB_HOST=postgresql-shared-rw.postgresql-system.svc.cluster.local
DB_DATABASE=pixelfed
DB_USERNAME=pixelfed
DB_PASSWORD=<REPLACE_WITH_DATABASE_PASSWORD>
```
#### **Redis Configuration**
```bash
REDIS_HOST=redis-ha-haproxy.redis-system.svc.cluster.local
REDIS_PORT=6379
REDIS_PASSWORD=<REPLACE_WITH_REDIS_PASSWORD>
```
#### **S3 Media Storage (Backblaze B2)**
```bash
# Enable cloud storage with dedicated bucket approach
PF_ENABLE_CLOUD=true
DANGEROUSLY_SET_FILESYSTEM_DRIVER=s3
FILESYSTEM_DRIVER=s3
FILESYSTEM_CLOUD=s3
FILESYSTEM_DISK=s3
# Backblaze B2 S3-compatible configuration
AWS_ACCESS_KEY_ID=<REPLACE_WITH_S3_ACCESS_KEY>
AWS_SECRET_ACCESS_KEY=<REPLACE_WITH_S3_SECRET_KEY>
AWS_DEFAULT_REGION=eu-central-003
AWS_BUCKET=pixelfed-bucket
AWS_URL=https://pm.keyboardvagabond.com/
AWS_ENDPOINT=<REPLACE_WITH_S3_ENDPOINT>
AWS_USE_PATH_STYLE_ENDPOINT=false
AWS_ROOT=
AWS_VISIBILITY=public
# CDN Configuration for media delivery
CDN_DOMAIN=pm.keyboardvagabond.com
```
#### **Email (SMTP)**
```bash
MAIL_MAILER=smtp
MAIL_HOST=<YOUR_SMTP_SERVER>
MAIL_PORT=587
MAIL_USERNAME=pixelfed@mail.keyboardvagabond.com
MAIL_PASSWORD=<REPLACE_WITH_EMAIL_PASSWORD>
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=pixelfed@mail.keyboardvagabond.com
MAIL_FROM_NAME="Pixelfed at Keyboard Vagabond"
```
### **Container-Specific Configuration**
#### **Web Container Only**
```bash
PIXELFED_INIT_CONTAINER=true # Only set on ONE web pod
```
#### **Worker Container Only**
```bash
PIXELFED_INIT_CONTAINER=false # Never set on worker pods
```
## 🎯 **Deployment Strategy**
### **Initialization Pattern**
1. **First Web Pod**: Set `PIXELFED_INIT_CONTAINER=true`
- Runs database migrations
- Generates application key
- Imports initial data
2. **Additional Web Pods**: Set `PIXELFED_INIT_CONTAINER=false`
- Skip initialization tasks
- Start faster
3. **All Worker Pods**: Set `PIXELFED_INIT_CONTAINER=false`
- Never run database migrations
- Focus on background processing
### **Scaling Recommendations**
#### **Web Containers**
- **Start**: 2 replicas for high availability
- **Scale Up**: When CPU > 70% or response time > 200ms
- **Resources**: 4 CPU, 4GB RAM (medium+ tier)
#### **Worker Containers**
- **Start**: 1 replica for basic workload
- **Scale Up**: When queue depth > 100 or processing lag > 5 minutes
- **Resources**: 2 CPU, 4GB RAM initially, scale to 4 CPU, 8GB for heavy federation
## 📊 **Monitoring Integration**
### **OpenObserve Dashboards**
#### **Web Container Metrics**
- HTTP response times
- Request rates by endpoint
- PHP-FPM pool status
- Nginx connection metrics
- Rate limiting effectiveness
#### **Worker Container Metrics**
- Queue processing rates
- Job failure rates
- Horizon supervisor status
- Memory usage for image processing
- Federation activity
### **Health Checks**
#### **Web**: HTTP-based health check
```bash
curl -f http://localhost:80/api/v1/instance
```
#### **Worker**: Horizon status check
```bash
php artisan horizon:status
```
## 🔄 **Updates & Maintenance**
### **Updating Pixelfed Version**
1. Update `PIXELFED_VERSION` in `pixelfed-base/Dockerfile`
2. Update `VERSION` in `build-all.sh`
3. Run `./build-all.sh`
4. Deploy web containers first, then workers
### **Rolling Updates**
```bash
# Update web containers first
kubectl rollout restart deployment pixelfed-web
# Wait for web to be healthy
kubectl rollout status deployment pixelfed-web
# Then update workers
kubectl rollout restart deployment pixelfed-worker
```
## 🛠️ **Troubleshooting**
### **Common Issues**
#### **Database Connection**
```bash
# Check from web container
kubectl exec -it pixelfed-web-xxx -- php artisan migrate:status
# Check from worker container
kubectl exec -it pixelfed-worker-xxx -- php artisan queue:work --once
```
#### **Queue Processing**
```bash
# Check Horizon status
kubectl exec -it pixelfed-worker-xxx -- php artisan horizon:status
# View queue stats
kubectl exec -it pixelfed-worker-xxx -- php artisan queue:work --once --verbose
```
#### **Storage Issues**
```bash
# Test S3 connection
kubectl exec -it pixelfed-web-xxx -- php artisan storage:link
# Check media upload
curl -v https://pixelfed.keyboardvagabond.com/api/v1/media
```
### **Performance Optimization**
#### **Web Container Tuning**
- Adjust PHP-FPM pool size in Dockerfile
- Tune Nginx worker connections
- Enable OPcache optimizations
#### **Worker Container Tuning**
- Increase Horizon worker processes
- Adjust queue processing timeouts
- Scale based on queue metrics
## 🔗 **Integration with Your Infrastructure**
### **Perfect Fit For Your Setup**
-**PostgreSQL**: Uses your CloudNativePG cluster with read replicas
-**Redis**: Integrates with your Redis cluster
-**S3 Storage**: Leverages Backblaze B2 + Cloudflare CDN
-**Monitoring**: Ready for OpenObserve metrics collection
-**SSL**: Works with your cert-manager + Let's Encrypt setup
-**DNS**: Compatible with external-dns + Cloudflare
-**Auth**: Ready for Authentik SSO integration
### **Next Steps**
1. ✅ Build containers with `./build-all.sh`
2. ✅ Create Kubernetes manifests for both deployments
3. ✅ Set up PostgreSQL database and user
4. ✅ Configure ingress for `pixelfed.keyboardvagabond.com`
5. ❌ Integrate with Authentik for SSO
6. ❌ Configure Cloudflare Turnstile for spam protection
7. ✅ Use enhanced spam filter instead of recaptcha
---
**Built with ❤️ for your sophisticated Kubernetes infrastructure**

112
build/pixelfed/build-all.sh Executable file
View File

@@ -0,0 +1,112 @@
#!/bin/bash
set -e
# Configuration
REGISTRY="<YOUR_REGISTRY_URL>"
VERSION="v0.12.6"
PLATFORM="linux/arm64"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
echo -e "${GREEN}Building Pixelfed ${VERSION} Containers for ARM64...${NC}"
echo -e "${BLUE}This will build:${NC}"
echo -e "${YELLOW}pixelfed-base${NC} - Shared base image"
echo -e "${YELLOW}pixelfed-web${NC} - Web server (Nginx + PHP-FPM)"
echo -e "${YELLOW}pixelfed-worker${NC} - Background workers (Horizon + Scheduler)"
echo
# Build base image first
echo -e "${YELLOW}Step 1/3: Building base image...${NC}"
cd pixelfed-base
docker build \
--network=host \
--platform $PLATFORM \
--tag pixelfed-base:$VERSION \
--tag pixelfed-base:latest \
.
cd ..
echo -e "${GREEN}✓ Base image built successfully!${NC}"
# Build web container
echo -e "${YELLOW}Step 2/3: Building web container...${NC}"
cd pixelfed-web
docker build \
--network=host \
--platform $PLATFORM \
--tag $REGISTRY/library/pixelfed-web:$VERSION \
--tag $REGISTRY/library/pixelfed-web:latest \
.
cd ..
echo -e "${GREEN}✓ Web container built successfully!${NC}"
# Build worker container
echo -e "${YELLOW}Step 3/3: Building worker container...${NC}"
cd pixelfed-worker
docker build \
--network=host \
--platform $PLATFORM \
--tag $REGISTRY/library/pixelfed-worker:$VERSION \
--tag $REGISTRY/library/pixelfed-worker:latest \
.
cd ..
echo -e "${GREEN}✓ Worker container built successfully!${NC}"
echo -e "${GREEN}🎉 All containers built successfully!${NC}"
echo -e "${BLUE}Built containers:${NC}"
echo -e "${GREEN}$REGISTRY/library/pixelfed-web:$VERSION${NC}"
echo -e "${GREEN}$REGISTRY/library/pixelfed-worker:$VERSION${NC}"
# Ask about pushing to registry
echo
read -p "Push all containers to Harbor registry? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Pushing containers to registry...${NC}"
# Check if logged in
if ! docker info | grep -q "Username:"; then
echo -e "${YELLOW}Logging into Harbor registry...${NC}"
docker login $REGISTRY
fi
# Push web container
echo -e "${BLUE}Pushing web container...${NC}"
docker push $REGISTRY/library/pixelfed-web:$VERSION
docker push $REGISTRY/library/pixelfed-web:latest
# Push worker container
echo -e "${BLUE}Pushing worker container...${NC}"
docker push $REGISTRY/library/pixelfed-worker:$VERSION
docker push $REGISTRY/library/pixelfed-worker:latest
echo -e "${GREEN}✓ All containers pushed successfully!${NC}"
echo -e "${GREEN}Images available at:${NC}"
echo -e "${BLUE}$REGISTRY/library/pixelfed-web:$VERSION${NC}"
echo -e "${BLUE}$REGISTRY/library/pixelfed-worker:$VERSION${NC}"
else
echo -e "${YELLOW}Build completed. To push later, run:${NC}"
echo "docker push $REGISTRY/library/pixelfed-web:$VERSION"
echo "docker push $REGISTRY/library/pixelfed-web:latest"
echo "docker push $REGISTRY/library/pixelfed-worker:$VERSION"
echo "docker push $REGISTRY/library/pixelfed-worker:latest"
fi
# Clean up build cache
echo
read -p "Clean up build cache? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}Cleaning up build cache...${NC}"
docker builder prune -f
echo -e "${GREEN}✓ Build cache cleaned!${NC}"
fi
echo -e "${GREEN}🚀 All done! Ready for Kubernetes deployment.${NC}"

View File

@@ -0,0 +1,208 @@
# Multi-stage build for Pixelfed - optimized base image
FROM php:8.3-fpm-alpine AS builder
# Set environment variables
ENV PIXELFED_VERSION=v0.12.6
ENV TZ=UTC
ENV APP_ENV=production
ENV APP_DEBUG=false
# Use HTTP repositories and install build dependencies
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/main" > /etc/apk/repositories \
&& echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/community" >> /etc/apk/repositories \
&& apk update \
&& apk add --no-cache \
ca-certificates \
git \
curl \
zip \
unzip \
# Build dependencies for PHP extensions
libpng-dev \
oniguruma-dev \
libxml2-dev \
freetype-dev \
libjpeg-turbo-dev \
libzip-dev \
postgresql-dev \
icu-dev \
gettext-dev \
imagemagick-dev \
# Node.js and build tools for asset compilation
nodejs \
npm \
# Compilation tools for native modules
build-base \
python3 \
make \
# Additional build tools for PECL extensions
autoconf \
pkgconfig \
$PHPIZE_DEPS
# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_pgsql \
pgsql \
gd \
zip \
intl \
bcmath \
exif \
pcntl \
opcache \
# Install ImageMagick PHP extension via PECL
&& pecl install imagick \
&& docker-php-ext-enable imagick
# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/pixelfed
# Create pixelfed user
RUN addgroup -g 1000 pixelfed \
&& adduser -u 1000 -G pixelfed -s /bin/sh -D pixelfed
# Clone Pixelfed source
RUN git clone --depth 1 --branch ${PIXELFED_VERSION} https://github.com/pixelfed/pixelfed.git . \
&& chown -R pixelfed:pixelfed /var/www/pixelfed
# Switch to pixelfed user for dependency installation
USER pixelfed
# Install PHP dependencies and clear any cached Laravel configuration
RUN composer install --no-dev --optimize-autoloader --no-interaction \
&& php artisan config:clear || true \
&& php artisan route:clear || true \
&& php artisan view:clear || true \
&& php artisan cache:clear || true \
&& rm -f bootstrap/cache/packages.php bootstrap/cache/services.php || true \
&& php artisan package:discover --ansi || true
# Install Node.js and build assets (skip post-install scripts to avoid node-datachannel compilation)
USER root
RUN apk add --no-cache nodejs npm
USER pixelfed
RUN echo "ignore-scripts=true" > .npmrc \
&& npm ci \
&& npm run production \
&& rm -rf node_modules .npmrc
# Switch back to root for final setup
USER root
# ================================
# Runtime stage - optimized final image
# ================================
FROM php:8.3-fpm-alpine AS pixelfed-base
# Set environment variables
ENV TZ=UTC
ENV APP_ENV=production
ENV APP_DEBUG=false
# Install only runtime dependencies (no -dev packages, no build tools)
RUN echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/main" > /etc/apk/repositories \
&& echo "http://dl-cdn.alpinelinux.org/alpine/v3.22/community" >> /etc/apk/repositories \
&& apk update \
&& apk add --no-cache \
ca-certificates \
curl \
su-exec \
dcron \
# Runtime libraries for PHP extensions (no -dev versions)
libpng \
oniguruma \
libxml2 \
freetype \
libjpeg-turbo \
libzip \
libpq \
icu \
gettext \
# Image optimization tools (runtime only)
jpegoptim \
optipng \
pngquant \
gifsicle \
imagemagick \
ffmpeg \
&& rm -rf /var/cache/apk/*
# Re-install PHP extensions in runtime stage (this ensures compatibility)
RUN apk add --no-cache --virtual .build-deps \
libpng-dev \
oniguruma-dev \
libxml2-dev \
freetype-dev \
libjpeg-turbo-dev \
libzip-dev \
postgresql-dev \
icu-dev \
gettext-dev \
imagemagick-dev \
# Additional build tools for PECL extensions
autoconf \
pkgconfig \
git \
$PHPIZE_DEPS \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
pdo_pgsql \
pgsql \
gd \
zip \
intl \
bcmath \
exif \
pcntl \
opcache \
# Install ImageMagick PHP extension from source (PHP 8.3 compatibility)
&& git clone https://github.com/Imagick/imagick.git --depth 1 /tmp/imagick \
&& cd /tmp/imagick \
&& git fetch origin master \
&& git switch master \
&& phpize \
&& ./configure \
&& make \
&& make install \
&& docker-php-ext-enable imagick \
&& rm -rf /tmp/imagick \
&& apk del .build-deps \
&& rm -rf /var/cache/apk/*
# Create pixelfed user
RUN addgroup -g 1000 pixelfed \
&& adduser -u 1000 -G pixelfed -s /bin/sh -D pixelfed
# Set working directory
WORKDIR /var/www/pixelfed
# Copy application from builder (source + compiled assets + vendor dependencies)
COPY --from=builder --chown=pixelfed:pixelfed /var/www/pixelfed /var/www/pixelfed
# Copy custom assets (logo, banners, etc.) to override defaults. Doesn't override the png versions.
COPY --chown=pixelfed:pixelfed custom-assets/img/*.svg /var/www/pixelfed/public/img/
# Clear any cached configuration files and set proper permissions
RUN rm -rf /var/www/pixelfed/bootstrap/cache/*.php || true \
&& chmod -R 755 /var/www/pixelfed/storage \
&& chmod -R 755 /var/www/pixelfed/bootstrap/cache \
&& chown -R pixelfed:pixelfed /var/www/pixelfed/bootstrap/cache
# Configure PHP for better performance
RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.revalidate_freq=0" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.max_accelerated_files=10000" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.memory_consumption=192" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.max_wasted_percentage=10" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.interned_strings_buffer=16" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
&& echo "opcache.fast_shutdown=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
# Copy shared entrypoint utilities
COPY entrypoint-common.sh /usr/local/bin/entrypoint-common.sh
RUN chmod +x /usr/local/bin/entrypoint-common.sh

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 161 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 159 KiB

View File

@@ -0,0 +1,116 @@
#!/bin/sh
set -e
# Common functions for Pixelfed containers
# Setup directories and create necessary structure
setup_directories() {
echo "Setting up directories..."
mkdir -p /var/www/pixelfed/storage
mkdir -p /var/www/pixelfed/bootstrap/cache
# CRITICAL FIX: Remove stale package discovery cache files
echo "Removing stale package discovery cache files..."
rm -f /var/www/pixelfed/bootstrap/cache/packages.php || true
rm -f /var/www/pixelfed/bootstrap/cache/services.php || true
}
# Wait for database to be ready
wait_for_database() {
echo "Waiting for database connection..."
cd /var/www/pixelfed
# Try for up to 60 seconds
for i in $(seq 1 12); do
if su-exec pixelfed php artisan migrate:status >/dev/null 2>&1; then
echo "Database is ready!"
return 0
fi
echo "Database not ready yet, waiting... (attempt $i/12)"
sleep 5
done
echo "ERROR: Database connection failed after 60 seconds"
exit 1
}
# Run database migrations (only if needed)
setup_database() {
echo "Checking database migrations..."
cd /var/www/pixelfed
# Only run migrations if they haven't been run
if ! su-exec pixelfed php artisan migrate:status | grep -q "Y"; then
echo "Running database migrations..."
su-exec pixelfed php artisan migrate --force
else
echo "Database migrations are up to date"
fi
}
# Generate application key if not set
setup_app_key() {
if [ -z "$APP_KEY" ] || [ "$APP_KEY" = "base64:" ]; then
echo "Generating application key..."
cd /var/www/pixelfed
su-exec pixelfed php artisan key:generate --force
fi
}
# Cache configuration (safe to run multiple times)
cache_config() {
echo "Clearing and caching configuration..."
cd /var/www/pixelfed
# Clear all caches first to avoid stale service provider registrations
su-exec pixelfed php artisan config:clear || true
su-exec pixelfed php artisan route:clear || true
su-exec pixelfed php artisan view:clear || true
su-exec pixelfed php artisan cache:clear || true
# Remove package discovery cache files and regenerate them
rm -f bootstrap/cache/packages.php bootstrap/cache/services.php || true
su-exec pixelfed php artisan package:discover --ansi || true
# Now rebuild caches with fresh configuration
su-exec pixelfed php artisan config:cache
su-exec pixelfed php artisan route:cache
su-exec pixelfed php artisan view:cache
}
# Link storage if not already linked
setup_storage_link() {
if [ ! -L "/var/www/pixelfed/public/storage" ]; then
echo "Linking storage..."
cd /var/www/pixelfed
su-exec pixelfed php artisan storage:link
fi
}
# Import location data (only on first run)
import_location_data() {
if [ ! -f "/var/www/pixelfed/.location-imported" ]; then
echo "Importing location data..."
cd /var/www/pixelfed
su-exec pixelfed php artisan import:cities || true
touch /var/www/pixelfed/.location-imported
fi
}
# Main initialization function
initialize_pixelfed() {
echo "Initializing Pixelfed..."
setup_directories
# Only the first container should run these
if [ "${PIXELFED_INIT_CONTAINER:-false}" = "true" ]; then
setup_database
setup_app_key
import_location_data
fi
cache_config
setup_storage_link
echo "Pixelfed initialization complete!"
}

View File

@@ -0,0 +1,46 @@
FROM pixelfed-base AS pixelfed-web
# Install Nginx and supervisor for the web container
RUN apk add --no-cache nginx supervisor
# Configure PHP-FPM for web workload
RUN sed -i 's/user = www-data/user = pixelfed/' /usr/local/etc/php-fpm.d/www.conf \
&& sed -i 's/group = www-data/group = pixelfed/' /usr/local/etc/php-fpm.d/www.conf \
&& sed -i 's/listen = 127.0.0.1:9000/listen = 9000/' /usr/local/etc/php-fpm.d/www.conf \
&& sed -i 's/;listen.allowed_clients = 127.0.0.1/listen.allowed_clients = 127.0.0.1/' /usr/local/etc/php-fpm.d/www.conf
# Web-specific PHP configuration for better performance
RUN echo "pm = dynamic" >> /usr/local/etc/php-fpm.d/www.conf \
&& echo "pm.max_children = 50" >> /usr/local/etc/php-fpm.d/www.conf \
&& echo "pm.start_servers = 5" >> /usr/local/etc/php-fpm.d/www.conf \
&& echo "pm.min_spare_servers = 5" >> /usr/local/etc/php-fpm.d/www.conf \
&& echo "pm.max_spare_servers = 35" >> /usr/local/etc/php-fpm.d/www.conf \
&& echo "pm.max_requests = 500" >> /usr/local/etc/php-fpm.d/www.conf
# Copy web-specific configuration files
COPY nginx.conf /etc/nginx/nginx.conf
COPY supervisord-web.conf /etc/supervisor/conf.d/supervisord.conf
COPY entrypoint-web.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create nginx directories and set permissions
RUN mkdir -p /var/log/nginx \
&& mkdir -p /var/log/supervisor \
&& chown -R nginx:nginx /var/log/nginx
# Create SSL directories for cert-manager mounted certificates
RUN mkdir -p /etc/ssl/certs /etc/ssl/private \
&& chown -R nginx:nginx /etc/ssl
# Health check optimized for web container (check both HTTP and HTTPS)
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:80/api/v1/instance || curl -k -f https://localhost:443/api/v1/instance || exit 1
# Expose HTTP and HTTPS ports
EXPOSE 80 443
# Run as root to manage nginx and php-fpm
USER root
ENTRYPOINT ["/entrypoint.sh"]
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

View File

@@ -0,0 +1,36 @@
#!/bin/sh
set -e
# Source common functions
. /usr/local/bin/entrypoint-common.sh
echo "Starting Pixelfed Web Container..."
# Create web-specific directories
mkdir -p /var/log/nginx
mkdir -p /var/log/supervisor
mkdir -p /var/www/pixelfed/storage/nginx_temp/client_body
mkdir -p /var/www/pixelfed/storage/nginx_temp/proxy
mkdir -p /var/www/pixelfed/storage/nginx_temp/fastcgi
mkdir -p /var/www/pixelfed/storage/nginx_temp/uwsgi
mkdir -p /var/www/pixelfed/storage/nginx_temp/scgi
# Skip database initialization - handled by init-job
# Just set up basic directory structure and cache
echo "Setting up web container..."
setup_directories
# Cache configuration (Laravel needs this to run)
echo "Loading configuration cache..."
cd /var/www/pixelfed
php artisan config:cache || echo "Config cache failed, continuing..."
# Create storage symlink (needs to happen after every restart)
echo "Creating storage symlink..."
php artisan storage:link || echo "Storage link already exists or failed, continuing..."
echo "Web container initialization complete!"
echo "Starting Nginx and PHP-FPM..."
# Execute the main command (supervisord)
exec "$@"

View File

@@ -0,0 +1,315 @@
worker_processes auto;
error_log /dev/stderr warn;
pid /var/www/pixelfed/storage/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Configure temp paths that pixelfed user can write to
client_body_temp_path /var/www/pixelfed/storage/nginx_temp/client_body;
proxy_temp_path /var/www/pixelfed/storage/nginx_temp/proxy;
fastcgi_temp_path /var/www/pixelfed/storage/nginx_temp/fastcgi;
uwsgi_temp_path /var/www/pixelfed/storage/nginx_temp/uwsgi;
scgi_temp_path /var/www/pixelfed/storage/nginx_temp/scgi;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/stdout main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 20M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
application/activity+json
application/ld+json
image/svg+xml;
# HTTP server block (port 80)
server {
listen 80;
server_name _;
root /var/www/pixelfed/public;
index index.php;
charset utf-8;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.hcaptcha.com https://hcaptcha.com; style-src 'self' 'unsafe-inline' https://hcaptcha.com; img-src 'self' data: blob: https: http: https://imgs.hcaptcha.com; media-src 'self' https: http:; connect-src 'self' https://hcaptcha.com; font-src 'self' data:; frame-src https://hcaptcha.com https://*.hcaptcha.com; frame-ancestors 'none';" always;
# Hide nginx version
server_tokens off;
# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Error handling - pass 404s to Laravel/Pixelfed (CRITICAL for routing)
error_page 404 /index.php;
# Favicon and robots
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
# PHP-FPM processing - simplified like official Pixelfed
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Let nginx ingress and Laravel config handle HTTPS detection
# Optimized for web workload
fastcgi_buffering on;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_read_timeout 300;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 300;
}
# CSS and JS files - shorter cache for updates
location ~* \.(css|js) {
expires 7d;
add_header Cache-Control "public, max-age=604800";
access_log off;
try_files $uri $uri/ /index.php?$query_string;
}
# Font files - medium cache
location ~* \.(woff|woff2|ttf|eot) {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
try_files $uri $uri/ /index.php?$query_string;
}
# Media files - long cache (user uploads don't change)
location ~* \.(jpg|jpeg|png|gif|webp|avif|heic|mp4|webm|mov)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000";
access_log off;
# Try local first, fallback to S3 CDN for media
try_files $uri @media_fallback;
}
# Icons and SVG - medium cache
location ~* \.(ico|svg) {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
try_files $uri $uri/ /index.php?$query_string;
}
# ActivityPub and federation endpoints
location ~* ^/(\.well-known|api|oauth|outbox|following|followers) {
try_files $uri $uri/ /index.php?$query_string;
}
# Health check endpoint
location = /api/v1/instance {
try_files $uri $uri/ /index.php?$query_string;
}
# Pixelfed mobile app endpoints
location ~* ^/api/v1/(accounts|statuses|timelines|notifications) {
try_files $uri $uri/ /index.php?$query_string;
}
# Pixelfed discover and search
location ~* ^/(discover|search) {
try_files $uri $uri/ /index.php?$query_string;
}
# Media fallback to CDN (if using S3)
location @media_fallback {
return 302 https://pm.keyboardvagabond.com$uri;
}
# Deny access to hidden files
location ~ /\.(?!well-known).* {
deny all;
}
# Block common bot/scanner requests
location ~* (wp-admin|wp-login|phpMyAdmin|phpmyadmin) {
return 444;
}
}
# HTTPS server block (port 443) - for Cloudflare tunnel internal TLS
server {
listen 443 ssl;
server_name _;
root /var/www/pixelfed/public;
index index.php;
charset utf-8;
# cert-manager generated SSL certificate for internal communication
ssl_certificate /etc/ssl/certs/tls.crt;
ssl_certificate_key /etc/ssl/private/tls.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security headers (same as HTTP block)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.hcaptcha.com https://hcaptcha.com; style-src 'self' 'unsafe-inline' https://hcaptcha.com; img-src 'self' data: blob: https: http: https://imgs.hcaptcha.com; media-src 'self' https: http:; connect-src 'self' https://hcaptcha.com; font-src 'self' data:; frame-src https://hcaptcha.com https://*.hcaptcha.com; frame-ancestors 'none';" always;
# Hide nginx version
server_tokens off;
# Main location block
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Error handling - pass 404s to Laravel/Pixelfed (CRITICAL for routing)
error_page 404 /index.php;
# Favicon and robots
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
# PHP-FPM processing - same as HTTP block
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
# Set HTTPS environment for Laravel
fastcgi_param HTTPS on;
fastcgi_param SERVER_PORT 443;
# Optimized for web workload
fastcgi_buffering on;
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_read_timeout 300;
fastcgi_connect_timeout 60;
fastcgi_send_timeout 300;
}
# Static file handling (same as HTTP block)
location ~* \.(css|js) {
expires 7d;
add_header Cache-Control "public, max-age=604800";
access_log off;
try_files $uri $uri/ /index.php?$query_string;
}
location ~* \.(woff|woff2|ttf|eot) {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
try_files $uri $uri/ /index.php?$query_string;
}
location ~* \.(jpg|jpeg|png|gif|webp|avif|heic|mp4|webm|mov)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000";
access_log off;
try_files $uri @media_fallback;
}
location ~* \.(ico|svg) {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
try_files $uri $uri/ /index.php?$query_string;
}
# ActivityPub and federation endpoints
location ~* ^/(\.well-known|api|oauth|outbox|following|followers) {
try_files $uri $uri/ /index.php?$query_string;
}
# Health check endpoint
location = /api/v1/instance {
try_files $uri $uri/ /index.php?$query_string;
}
# Pixelfed mobile app endpoints
location ~* ^/api/v1/(accounts|statuses|timelines|notifications) {
try_files $uri $uri/ /index.php?$query_string;
}
# Pixelfed discover and search
location ~* ^/(discover|search) {
try_files $uri $uri/ /index.php?$query_string;
}
# Media fallback to CDN (if using S3)
location @media_fallback {
return 302 https://pm.keyboardvagabond.com$uri;
}
# Deny access to hidden files
location ~ /\.(?!well-known).* {
deny all;
}
# Block common bot/scanner requests
location ~* (wp-admin|wp-login|phpMyAdmin|phpmyadmin) {
return 444;
}
}
}

View File

@@ -0,0 +1,43 @@
[supervisord]
nodaemon=true
logfile=/dev/stdout
logfile_maxbytes=0
pidfile=/tmp/supervisord.pid
[unix_http_server]
file=/tmp/supervisor.sock
chmod=0700
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[program:nginx]
command=nginx -g "daemon off;"
autostart=true
autorestart=true
startretries=5
numprocs=1
startsecs=0
process_name=%(program_name)s_%(process_num)02d
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
priority=100
[program:php-fpm]
command=php-fpm --nodaemonize
autostart=true
autorestart=true
startretries=5
numprocs=1
startsecs=0
process_name=%(program_name)s_%(process_num)02d
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
priority=200

View File

@@ -0,0 +1,28 @@
FROM pixelfed-base AS pixelfed-worker
# Install supervisor for worker management
RUN apk add --no-cache supervisor
# Worker-specific PHP configuration for background processing
RUN echo "memory_limit = 512M" >> /usr/local/etc/php/conf.d/worker.ini \
&& echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/worker.ini \
&& echo "max_input_time = 300" >> /usr/local/etc/php/conf.d/worker.ini \
&& echo "pcntl.enabled = 1" >> /usr/local/etc/php/conf.d/worker.ini
# Copy worker-specific configuration files
COPY supervisord-worker.conf /etc/supervisor/conf.d/supervisord.conf
COPY entrypoint-worker.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create supervisor directories
RUN mkdir -p /var/log/supervisor
# Health check for worker container (check horizon status)
HEALTHCHECK --interval=60s --timeout=10s --start-period=60s --retries=3 \
CMD su-exec pixelfed php /var/www/pixelfed/artisan horizon:status || exit 1
# Run as root to manage processes
USER root
ENTRYPOINT ["/entrypoint.sh"]
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

View File

@@ -0,0 +1,58 @@
#!/bin/sh
set -e
# Source common functions
. /usr/local/bin/entrypoint-common.sh
echo "Starting Pixelfed Worker Container..."
# CRITICAL FIX: Remove stale package discovery cache files FIRST
echo "Removing stale package discovery cache files..."
rm -f /var/www/pixelfed/bootstrap/cache/packages.php || true
rm -f /var/www/pixelfed/bootstrap/cache/services.php || true
rm -f /var/www/pixelfed/bootstrap/cache/config.php || true
# Create worker-specific directories
mkdir -p /var/log/supervisor
# Skip database initialization - handled by init-job
# Just set up basic directory structure
echo "Setting up worker container..."
setup_directories
# Wait for database to be ready (but don't initialize)
echo "Waiting for database connection..."
cd /var/www/pixelfed
for i in $(seq 1 12); do
if php artisan migrate:status >/dev/null 2>&1; then
echo "Database is ready!"
break
fi
echo "Database not ready yet, waiting... (attempt $i/12)"
sleep 5
done
# Clear Laravel caches to ensure fresh service provider registration
echo "Clearing Laravel caches and regenerating package discovery..."
php artisan config:clear || true
php artisan route:clear || true
php artisan view:clear || true
php artisan cache:clear || true
# Remove and regenerate package discovery cache
rm -f bootstrap/cache/packages.php bootstrap/cache/services.php || true
php artisan package:discover --ansi || true
# Clear and restart Horizon queues
echo "Preparing Horizon queue system..."
# Clear any existing queue data
php artisan horizon:clear || true
# Publish Horizon assets if needed
php artisan horizon:publish || true
echo "Worker container initialization complete!"
echo "Starting Laravel Horizon and Scheduler..."
# Execute the main command (supervisord)
exec "$@"

View File

@@ -0,0 +1,67 @@
[supervisord]
nodaemon=true
logfile=/dev/stdout
logfile_maxbytes=0
pidfile=/tmp/supervisord.pid
[unix_http_server]
file=/tmp/supervisor.sock
chmod=0700
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[program:horizon]
command=php /var/www/pixelfed/artisan horizon
directory=/var/www/pixelfed
user=pixelfed
autostart=true
autorestart=true
startretries=5
numprocs=1
startsecs=0
process_name=%(program_name)s_%(process_num)02d
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
priority=100
# Kill horizon gracefully on stop
stopsignal=TERM
stopwaitsecs=60
[program:schedule]
command=php /var/www/pixelfed/artisan schedule:work
directory=/var/www/pixelfed
user=pixelfed
autostart=true
autorestart=true
startretries=5
numprocs=1
startsecs=0
process_name=%(program_name)s_%(process_num)02d
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
priority=200
# Additional worker for high-priority queues (including media)
[program:high-priority-worker]
command=php /var/www/pixelfed/artisan queue:work --queue=high,mmo,default --sleep=1 --tries=3 --max-time=1800
directory=/var/www/pixelfed
user=pixelfed
autostart=true
autorestart=true
startretries=5
numprocs=1
startsecs=0
process_name=%(program_name)s_%(process_num)02d
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
priority=300