This commit is contained in:
myrmidex 2025-11-16 15:53:52 +01:00
parent a7036cda3d
commit a5307f3e5d
13 changed files with 1818 additions and 40 deletions

63
.dockerignore Normal file
View file

@ -0,0 +1,63 @@
# Git
.git/
.github/
.gitignore
.gitattributes
# CI/CD
.woodpecker.yml
# Environment
.env
.env.*
!.env.example
# Development
.editorconfig
.eslintrc*
.prettierrc*
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Documentation
*.md
LICENSE
# Docker
docker-compose*.yml
# Claude files
.claude/
# Node modules (will be installed in build)
node_modules/
frontend/node_modules/
# Backend vendor (will be installed in build)
vendor/
backend/vendor/
# Build artifacts
frontend/dist/
frontend/build/
# Cache and logs
backend/storage/logs/*
backend/storage/framework/cache/*
backend/storage/framework/sessions/*
backend/storage/framework/views/*
backend/bootstrap/cache/*
*.log
# Tests
tests/
backend/tests/
frontend/tests/
# Data directories
docker/data/

6
.env Normal file
View file

@ -0,0 +1,6 @@
APP_KEY=base64:dGVzdGluZ19rZXlfZm9yX3Byb2R1Y3Rpb25fdGVzdGluZ19vbmx5X25vdF9zZWN1cmU=
DB_PASSWORD=test_password_123
MYSQL_ROOT_PASSWORD=root_password_456
DB_USERNAME=trip_user
DB_DATABASE=trip_planner
APP_URL=http://localhost:8080

100
.woodpecker.yml Normal file
View file

@ -0,0 +1,100 @@
---
# Woodpecker CI Pipeline for Trip Planner
# Builds and pushes production Docker image to Codeberg Container Registry
when:
- event: push
branch: main
variables:
- &image_repo 'codeberg.org/lvl0/trip-planner'
steps:
# Extract version from commit message if merging from release branch
- name: extract-version
image: alpine:latest
commands:
- |
# Install git and grep with PCRE support
apk add --no-cache git grep
# Get the commit message to check if it's a merge from release branch
COMMIT_MSG=$(git log -1 --pretty=%B)
# Try to extract version from merge commit message (e.g., "Merge branch 'release/v0.1.0'")
VERSION=$(echo "$COMMIT_MSG" | grep -oP "release/v?\K[0-9]+\.[0-9]+\.[0-9]+" || echo "")
if [ -z "$VERSION" ]; then
# No version found, use commit SHA as version
VERSION="dev-$(git rev-parse --short HEAD)"
echo "No release version detected, using: $VERSION"
echo "$VERSION" > /woodpecker/version.txt
else
echo "Detected release version: $VERSION"
echo "$VERSION" > /woodpecker/version.txt
# Export for use in build step
echo "export VERSION_TAG=$VERSION" >> /tmp/version_env.sh
fi
# Build and push with latest tag (always)
- name: build-and-push-latest
image: woodpeckerci/plugin-docker-buildx
settings:
repo: *image_repo
registry: codeberg.org
username:
from_secret: container_registry_username
password:
from_secret: container_registry_token
dockerfile: Dockerfile.prod
context: .
platforms: linux/amd64
build_args:
- BUILDKIT_INLINE_CACHE=1
auto_tag: false
tags:
- latest
depends_on:
- extract-version
# Check if this is a release build
- name: check-release
image: alpine:latest
commands:
- |
VERSION=$(cat /woodpecker/version.txt)
if echo "$VERSION" | grep -qv "^dev-"; then
echo "Release version detected: $VERSION"
echo "true" > /woodpecker/is_release.txt
else
echo "Development build ($VERSION), will skip version tag"
echo "false" > /woodpecker/is_release.txt
fi
depends_on:
- build-and-push-latest
# Notify build status
- name: notify-success
image: alpine:latest
commands:
- |
VERSION=$(cat /woodpecker/version.txt 2>/dev/null || echo "unknown")
IS_RELEASE=$(cat /woodpecker/is_release.txt 2>/dev/null || echo "false")
echo "✅ Successfully built and pushed trip-planner:latest"
echo "Version: $VERSION"
if [ "$IS_RELEASE" = "true" ]; then
echo " This is a release build. To tag with version $VERSION:"
echo " docker pull codeberg.org/lvl0/trip-planner:latest"
echo " docker tag codeberg.org/lvl0/trip-planner:latest codeberg.org/lvl0/trip-planner:$VERSION"
echo " docker push codeberg.org/lvl0/trip-planner:$VERSION"
fi
echo "Image: codeberg.org/lvl0/trip-planner:latest"
when:
status: success
- name: notify-failure
image: alpine:latest
commands:
- echo "❌ Build failed! Check the logs above for details."
when:
status: failure

134
Dockerfile.prod Normal file
View file

@ -0,0 +1,134 @@
# =============================================================================
# Stage 1: Build Frontend
# =============================================================================
FROM node:20-alpine AS frontend-builder
WORKDIR /app/frontend
# Copy frontend package files
COPY frontend/package*.json ./
# Install dependencies (including dev for build)
RUN npm ci
# Copy frontend source
COPY frontend/ ./
# Build frontend
RUN npm run build
# =============================================================================
# Stage 2: Build Backend Dependencies
# =============================================================================
FROM php:8.3-fpm-alpine AS backend-builder
# Install build dependencies
RUN apk add --no-cache \
libpng-dev \
oniguruma-dev \
libxml2-dev \
zip \
unzip
# Install PHP extensions
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd opcache
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html
# Copy composer files
COPY backend/composer.json backend/composer.lock ./
# Install PHP dependencies (production only)
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist
# Copy backend source
COPY backend/ ./
# Generate optimized autoloader
RUN composer dump-autoload --optimize --classmap-authoritative
# =============================================================================
# Stage 3: Final Production Image (All-in-One)
# =============================================================================
FROM php:8.3-fpm-alpine
# Install runtime dependencies
RUN apk add --no-cache \
libpng \
oniguruma \
libxml2 \
nginx \
supervisor \
mariadb \
mariadb-client \
redis \
curl \
bash
# Copy PHP extensions from backend-builder
COPY --from=backend-builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=backend-builder /usr/local/etc/php/conf.d/docker-php-ext-*.ini /usr/local/etc/php/conf.d/
# Configure PHP for production
RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/opcache.ini && \
echo "opcache.memory_consumption=128" >> /usr/local/etc/php/conf.d/opcache.ini && \
echo "opcache.max_accelerated_files=10000" >> /usr/local/etc/php/conf.d/opcache.ini && \
echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/opcache.ini && \
echo "expose_php=0" >> /usr/local/etc/php/conf.d/security.ini && \
echo "display_errors=0" >> /usr/local/etc/php/conf.d/security.ini && \
echo "log_errors=1" >> /usr/local/etc/php/conf.d/security.ini
# Create application user
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser && \
mkdir -p /var/www/html /usr/share/nginx/html && \
chown -R appuser:appuser /var/www/html /usr/share/nginx/html
# Create necessary directories for services
RUN mkdir -p /run/nginx /run/mysqld /var/lib/mysql /var/log/supervisor /data/redis && \
chown -R appuser:appuser /run/nginx /usr/share/nginx/html && \
chown -R mysql:mysql /run/mysqld /var/lib/mysql && \
chown -R redis:redis /data/redis
# Copy backend from builder
WORKDIR /var/www/html
COPY --from=backend-builder --chown=appuser:appuser /var/www/html ./
# Create Laravel required directories
RUN mkdir -p storage/framework/{sessions,views,cache} \
storage/logs \
bootstrap/cache && \
chown -R appuser:appuser storage bootstrap/cache && \
chmod -R 775 storage bootstrap/cache
# Copy frontend built assets
COPY --from=frontend-builder --chown=appuser:appuser /app/frontend/dist /usr/share/nginx/html
# Copy nginx configurations
COPY docker/nginx/production.conf /etc/nginx/http.d/default.conf
# Copy supervisor configuration
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Initialize MariaDB data directory
RUN mysql_install_db --user=mysql --datadir=/var/lib/mysql
# Copy entrypoint script
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost/up || exit 1
# Expose HTTP port
EXPOSE 80
# Set entrypoint
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

48
backend/.dockerignore Normal file
View file

@ -0,0 +1,48 @@
# Dependencies
node_modules/
vendor/
# Environment files
.env
.env.*
!.env.example
# Testing
tests/
.phpunit.result.cache
phpunit.xml
# Development files
.git/
.github/
.gitignore
.gitattributes
.editorconfig
.php-cs-fixer.php
.php-cs-fixer.cache
phpstan.neon
phpstan-baseline.neon
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Storage and cache (will be generated in container)
storage/framework/cache/*
storage/framework/sessions/*
storage/framework/views/*
storage/logs/*
bootstrap/cache/*
# Documentation
README.md
CHANGELOG.md
LICENSE
# Docker files
Dockerfile*
docker-compose*.yml
.dockerignore

View file

@ -1,58 +1,66 @@
version: '3.8' version: '3.8'
services: services:
frontend: app:
build: build:
context: ./frontend context: .
dockerfile: ../docker/frontend/Dockerfile.prod dockerfile: Dockerfile.prod
container_name: trip-planner-frontend container_name: trip-planner-production
ports: ports:
- "${FRONTEND_PORT:-80}:80" - "${APP_PORT:-8080}:80"
restart: unless-stopped
networks:
- trip-planner-network
backend:
build:
context: ./backend
dockerfile: ../docker/backend/Dockerfile.prod
container_name: trip-planner-backend
ports:
- "${BACKEND_PORT:-8080}:80"
environment: environment:
# Laravel Application
APP_NAME: "Trip Planner"
APP_ENV: production APP_ENV: production
APP_DEBUG: false APP_DEBUG: false
APP_URL: ${APP_URL} APP_KEY: ${APP_KEY}
APP_URL: ${APP_URL:-http://localhost:8080}
# Database (internal MariaDB)
DB_CONNECTION: mysql DB_CONNECTION: mysql
DB_HOST: ${DB_HOST} DB_HOST: 127.0.0.1
DB_PORT: ${DB_PORT:-3306} DB_PORT: 3306
DB_DATABASE: ${DB_DATABASE} DB_DATABASE: ${DB_DATABASE:-trip_planner}
DB_USERNAME: ${DB_USERNAME} DB_USERNAME: ${DB_USERNAME:-trip_user}
DB_PASSWORD: ${DB_PASSWORD} DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD must be set}
REDIS_HOST: redis MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD must be set}
# Redis (internal)
REDIS_HOST: 127.0.0.1
REDIS_PORT: 6379 REDIS_PORT: 6379
REDIS_PASSWORD: null
# Cache & Session
CACHE_DRIVER: redis CACHE_DRIVER: redis
QUEUE_CONNECTION: redis QUEUE_CONNECTION: redis
SESSION_DRIVER: redis SESSION_DRIVER: redis
depends_on: SESSION_LIFETIME: 120
- redis
restart: unless-stopped
networks:
- trip-planner-network
redis: # Mail (configure as needed)
image: docker.io/library/redis:alpine MAIL_MAILER: ${MAIL_MAILER:-log}
container_name: trip-planner-redis MAIL_HOST: ${MAIL_HOST:-}
MAIL_PORT: ${MAIL_PORT:-}
MAIL_USERNAME: ${MAIL_USERNAME:-}
MAIL_PASSWORD: ${MAIL_PASSWORD:-}
MAIL_ENCRYPTION: ${MAIL_ENCRYPTION:-}
MAIL_FROM_ADDRESS: ${MAIL_FROM_ADDRESS:-noreply@tripplanner.local}
MAIL_FROM_NAME: "${MAIL_FROM_NAME:-Trip Planner}"
volumes: volumes:
- redis-data:/data # Persistent data for database
command: redis-server --appendonly yes - db-data:/var/lib/mysql
# Persistent data for redis
- redis-data:/data/redis
# Persistent storage for uploaded files
- storage-data:/var/www/html/storage/app
restart: unless-stopped restart: unless-stopped
networks: healthcheck:
- trip-planner-network test: ["CMD", "curl", "-f", "http://localhost/up"]
interval: 30s
networks: timeout: 10s
trip-planner-network: retries: 3
driver: bridge start_period: 60s
volumes: volumes:
db-data:
redis-data: redis-data:
storage-data:

160
docker/README.md Normal file
View file

@ -0,0 +1,160 @@
# Trip Planner - Docker Setup
This directory contains Docker configurations for both development and production environments.
## Directory Structure
```
docker/
├── README.md # This file
├── dev/ # Development environment documentation
│ └── README.md
├── prod/ # Production environment documentation
│ └── README.md
├── backend/ # Backend service configurations
│ ├── Dockerfile.dev
│ ├── Dockerfile.prod
│ └── supervisord.conf
├── frontend/ # Frontend service configurations
│ ├── Dockerfile.dev
│ └── Dockerfile.prod
├── nginx/ # Nginx configurations
│ ├── backend.conf # Backend API nginx config
│ ├── frontend.conf # Frontend SPA nginx config
│ └── production.conf # All-in-one production nginx config
├── supervisord.conf # Supervisord config for production
└── entrypoint.sh # Production container entrypoint script
```
## Environments
### Development Environment
**Architecture**: Multi-container setup with separate services
- Frontend (React + Vite dev server)
- Backend (Laravel + PHP-FPM)
- Database (MariaDB)
- Redis
- Mailpit (email testing)
**Use case**: Local development with hot-reloading and debugging
**Documentation**: See [dev/README.md](dev/README.md)
**Docker Compose**: `docker-compose.dev.yml` (in project root)
### Production Environment
**Architecture**: Single all-in-one container with all services
- Frontend (built React app served by Nginx)
- Backend (Laravel + PHP-FPM + Nginx)
- Database (MariaDB - internal)
- Redis (internal)
- All managed by Supervisord
**Use case**: Production deployment with minimal footprint
**Documentation**: See [prod/README.md](prod/README.md)
**Docker Compose**: `docker-compose.prod.yml` (in project root)
## Key Differences
| Aspect | Development | Production |
|--------|------------|------------|
| **Containers** | Multiple (5 services) | Single all-in-one |
| **Frontend** | Vite dev server with HMR | Pre-built static files |
| **Backend** | Live code mounting | Copied into image |
| **Database** | Separate container | Internal to main container |
| **Redis** | Separate container | Internal to main container |
| **Volumes** | Source code mounted | Persistent data only |
| **Ports** | Multiple (5173, 8000, 3306, etc.) | Single port (80) |
| **Size** | ~2GB+ | ~800MB |
## Port Allocation
### Development (default ports)
- Frontend: 5173
- Backend: 8000
- Database: 3306
- Redis: 6379
- Mailpit UI: 8025
- Mailpit SMTP: 1025
### Production (default ports)
- Application: 8080 (configurable via `APP_PORT`)
**Note**: When running both dev and production locally, ensure they don't use conflicting ports. The production setup defaults to port 8080 to avoid conflicts with the dev setup.
## Quick Start
### Development
```bash
# Start all dev services
docker compose -f docker-compose.dev.yml up
# Stop all dev services
docker compose -f docker-compose.dev.yml down
```
### Production (Local Testing)
```bash
# Build and start production container
docker compose -f docker-compose.prod.yml up --build
# Stop production container
docker compose -f docker-compose.prod.yml down
```
## Environment Variables
Both environments use environment variables for configuration:
- **Development**: `.env.local` in project root
- **Production**: `.env` or pass via docker-compose environment section
See the respective README files for detailed environment variable documentation.
## Building Images
### Development
Development images are built automatically when you run `docker compose up`.
### Production
```bash
# Build the production image
docker build -f Dockerfile.prod -t trip-planner:latest .
# Or use docker-compose
docker compose -f docker-compose.prod.yml build
```
## CI/CD
The production image is automatically built and pushed to Codeberg Container Registry when changes are merged to the `main` branch.
See `.woodpecker.yml` in the project root for pipeline configuration.
## Troubleshooting
### Development Issues
See [dev/README.md](dev/README.md#troubleshooting)
### Production Issues
See [prod/README.md](prod/README.md#troubleshooting)
## Security Notes
- Development setup runs with elevated privileges for convenience
- Production setup follows security best practices:
- Non-root users where possible
- Minimal base images
- No unnecessary privileges
- Security headers configured
- Internal services (DB, Redis) bound to localhost only
## Need Help?
- Check the specific environment README files in `dev/` or `prod/`
- Review the main project documentation
- Check container logs: `docker logs <container-name>`

342
docker/dev/README.md Normal file
View file

@ -0,0 +1,342 @@
# Trip Planner - Development Environment
This document describes the development Docker setup for Trip Planner.
## Overview
The development environment uses a multi-container architecture with Docker Compose, providing:
- **Hot Module Replacement (HMR)** for frontend development
- **Live code mounting** for instant backend changes
- **Separate services** for easy debugging
- **Development tools** like Mailpit for email testing
## Architecture
```
┌─────────────────────────────────────────────────────┐
│ trip-planner-network (Docker Bridge Network) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Frontend │ │ Backend │ │ Database │ │
│ │ React+Vite │ │ Laravel+PHP │ │ MariaDB │ │
│ │ Port: 5173 │ │ Port: 8000 │ │ Port:3306│ │
│ └──────────────┘ └──────────────┘ └──────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Redis │ │ Mailpit │ │
│ │ Port: 6379 │ │ UI: 8025 │ │
│ └──────────────┘ │ SMTP: 1025 │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────┘
```
## Services
### Frontend (trip-planner-frontend-dev)
- **Image**: Built from `docker/frontend/Dockerfile.dev`
- **Port**: 5173
- **Technology**: React 19 + Vite 7
- **Features**: Hot Module Replacement, ESLint
- **Volume**: `./frontend:/app` (live code mounting)
### Backend (trip-planner-backend-dev)
- **Image**: Built from `docker/backend/Dockerfile.dev`
- **Port**: 8000
- **Technology**: Laravel 12 + PHP 8.3
- **Features**: Artisan commands, PHP-FPM
- **Volume**: `./backend:/var/www/html` (live code mounting)
### Database (trip-planner-db-dev)
- **Image**: MariaDB 11
- **Port**: 3306
- **Data**: Persisted in `./docker/data/mysql-data`
- **Credentials**: Configured via `.env.local`
### Redis (trip-planner-redis-dev)
- **Image**: Redis Alpine
- **Port**: 6379
- **Usage**: Cache, sessions, queues
- **Data**: Named volume `redis-data`
### Mailpit (trip-planner-mailpit-dev)
- **Image**: Axllent Mailpit
- **Ports**:
- SMTP: 1025
- Web UI: 8025
- **Usage**: Email testing (catches all outgoing emails)
## Getting Started
### Prerequisites
- Docker Engine 20.10+
- Docker Compose 2.0+
- At least 4GB RAM available for Docker
### Initial Setup
1. **Clone the repository** (if not already done):
```bash
git clone ssh://git@codeberg.org/lvl0/trip-planner.git
cd trip-planner
```
2. **Create environment file**:
```bash
# Copy the example environment file
cp .env.local.example .env.local
# Edit .env.local with your settings
nano .env.local
```
3. **Start the development environment**:
```bash
docker compose -f docker-compose.dev.yml up -d
```
4. **Wait for services to be ready** (check with):
```bash
docker compose -f docker-compose.dev.yml ps
```
5. **Run initial Laravel setup**:
```bash
# Generate application key
docker exec trip-planner-backend-dev php artisan key:generate
# Run migrations
docker exec trip-planner-backend-dev php artisan migrate
# Seed database (optional)
docker exec trip-planner-backend-dev php artisan db:seed
```
6. **Access the application**:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8000
- Mailpit UI: http://localhost:8025
### Daily Development Workflow
```bash
# Start all services
docker compose -f docker-compose.dev.yml up -d
# View logs
docker compose -f docker-compose.dev.yml logs -f
# Stop all services
docker compose -f docker-compose.dev.yml down
# Restart a specific service
docker compose -f docker-compose.dev.yml restart backend
```
## Environment Variables
The development environment reads from `.env.local` in the project root.
### Required Variables
```env
# Application
APP_NAME="Trip Planner"
APP_KEY=base64:your-generated-key-here
# Database
DB_DATABASE=trip_planner
DB_USERNAME=trip_user
DB_PASSWORD=secret
# Mail
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
```
### Optional Variables
```env
# Frontend
VITE_API_URL=http://localhost:8000
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
```
## Common Tasks
### Backend Tasks
```bash
# Run Artisan commands
docker exec trip-planner-backend-dev php artisan <command>
# Examples:
docker exec trip-planner-backend-dev php artisan migrate
docker exec trip-planner-backend-dev php artisan make:controller UserController
docker exec trip-planner-backend-dev php artisan tinker
# Install PHP dependencies
docker exec trip-planner-backend-dev composer install
# Run tests
docker exec trip-planner-backend-dev php artisan test
# Clear caches
docker exec trip-planner-backend-dev php artisan cache:clear
docker exec trip-planner-backend-dev php artisan config:clear
docker exec trip-planner-backend-dev php artisan route:clear
```
### Frontend Tasks
```bash
# Install npm dependencies
docker exec trip-planner-frontend-dev npm install
# Run linter
docker exec trip-planner-frontend-dev npm run lint
# Build for preview
docker exec trip-planner-frontend-dev npm run build
```
### Database Tasks
```bash
# Access MySQL shell
docker exec -it trip-planner-db-dev mysql -u trip_user -p trip_planner
# Backup database
docker exec trip-planner-db-dev mysqldump -u trip_user -p trip_planner > backup.sql
# Restore database
docker exec -i trip-planner-db-dev mysql -u trip_user -p trip_planner < backup.sql
# Reset database
docker compose -f docker-compose.dev.yml down -v # Removes volumes!
docker compose -f docker-compose.dev.yml up -d
docker exec trip-planner-backend-dev php artisan migrate --seed
```
### Viewing Logs
```bash
# All services
docker compose -f docker-compose.dev.yml logs -f
# Specific service
docker compose -f docker-compose.dev.yml logs -f backend
# Laravel logs
docker exec trip-planner-backend-dev tail -f storage/logs/laravel.log
```
## Troubleshooting
### Services won't start
**Check for port conflicts:**
```bash
# Check what's using the ports
lsof -i :5173 # Frontend
lsof -i :8000 # Backend
lsof -i :3306 # Database
# Stop conflicting services or change ports in docker-compose.dev.yml
```
### Frontend HMR not working
**SELinux issue (Fedora/RHEL):**
The `:Z` flag in volume mounts handles this, but if HMR still doesn't work:
```bash
# Check if SELinux is enforcing
getenforce
# If needed, you can temporarily set to permissive
sudo setenforce 0
```
### Backend not connecting to database
**Wait for database to be fully ready:**
```bash
# Check database status
docker compose -f docker-compose.dev.yml ps database
# Check database logs
docker compose -f docker-compose.dev.yml logs database
# Verify connection
docker exec trip-planner-backend-dev php artisan migrate:status
```
### Permission issues
**Vendor/node_modules ownership:**
```bash
# Fix backend vendor permissions
docker exec trip-planner-backend-dev chown -R www-data:www-data vendor
# Fix frontend node_modules (usually not needed with named volumes)
docker compose -f docker-compose.dev.yml down
docker volume rm trip-planner_node_modules
docker compose -f docker-compose.dev.yml up -d
```
### Clean slate rebuild
```bash
# Stop everything
docker compose -f docker-compose.dev.yml down -v
# Remove images
docker rmi trip-planner-frontend-dev trip-planner-backend-dev
# Rebuild and start
docker compose -f docker-compose.dev.yml up --build
```
## Performance Tips
### Disable Features You Don't Need
If a service is not needed for your current task:
```bash
# Start only specific services
docker compose -f docker-compose.dev.yml up -d backend database redis
```
### Use Cached Volumes
The dev setup uses named volumes for `node_modules` and `vendor` to improve performance:
- `node_modules`: Frontend dependencies
- `vendor`: Backend PHP dependencies
These are NOT mounted from your host, keeping filesystem operations fast.
## Differences from Production
| Feature | Development | Production |
|---------|------------|------------|
| Code loading | Live mounted volumes | Copied into image |
| Caching | Disabled/minimal | Aggressive (OPcache, etc.) |
| Error display | Verbose | Hidden |
| Debug mode | Enabled | Disabled |
| Privileges | Elevated for convenience | Minimal (security) |
| Rebuilding | Rarely needed | Required for changes |
## Security Note
⚠️ **The development environment is NOT secure** - it runs with `privileged: true` for convenience and mounts source code directly. **Never use this setup in production!**
## Need Help?
- Check the main [docker/README.md](../README.md)
- Review Laravel logs: `docker exec trip-planner-backend-dev tail -f storage/logs/laravel.log`
- Check container health: `docker compose -f docker-compose.dev.yml ps`
- Inspect a container: `docker inspect trip-planner-backend-dev`

141
docker/entrypoint.sh Normal file
View file

@ -0,0 +1,141 @@
#!/bin/bash
set -e
# Configuration
MYSQL_WAIT_ATTEMPTS=15
MYSQL_WAIT_INTERVAL=2
REDIS_WAIT_ATTEMPTS=10
echo "========================================="
echo "[INIT] Trip Planner - Production Container Init"
echo "========================================="
# Validate required environment variables
if [ -z "$APP_KEY" ]; then
echo "[INIT] ERROR: APP_KEY is not set!"
exit 1
fi
# Laravel APP_KEY must be base64: prefix + minimum 44 chars (32 bytes base64-encoded)
if [[ ! "$APP_KEY" =~ ^base64:.{44,}$ ]]; then
echo "[INIT] ERROR: APP_KEY format is invalid! Must be base64:xxxxx (generated with 'php artisan key:generate')"
exit 1
fi
if [ -z "$DB_PASSWORD" ]; then
echo "[INIT] ERROR: DB_PASSWORD is not set!"
exit 1
fi
if [ -z "$MYSQL_ROOT_PASSWORD" ]; then
echo "[INIT] ERROR: MYSQL_ROOT_PASSWORD is not set!"
exit 1
fi
# Start MariaDB in background
echo "[INIT] Starting MariaDB..."
mysqld --user=mysql --datadir=/var/lib/mysql --bind-address=127.0.0.1 &
MYSQL_PID=$!
# Start Redis in background
echo "[INIT] Starting Redis..."
redis-server --bind 127.0.0.1 --port 6379 --dir /data/redis --appendonly yes --daemonize yes
# Wait for MariaDB to be ready
echo "[INIT] Waiting for MariaDB to be ready..."
for i in $(seq 1 $MYSQL_WAIT_ATTEMPTS); do
if mysqladmin ping --socket=/run/mysqld/mysqld.sock --silent 2>/dev/null; then
echo "[INIT] MariaDB is up! (took ${i} attempts)"
break
fi
if [ $i -eq $MYSQL_WAIT_ATTEMPTS ]; then
echo "[INIT] ERROR: MariaDB failed to start within $((MYSQL_WAIT_ATTEMPTS * MYSQL_WAIT_INTERVAL)) seconds"
kill $MYSQL_PID 2>/dev/null || true
exit 1
fi
sleep $MYSQL_WAIT_INTERVAL
done
# Wait for Redis to be ready
echo "[INIT] Waiting for Redis to be ready..."
for i in $(seq 1 $REDIS_WAIT_ATTEMPTS); do
if redis-cli ping 2>/dev/null | grep -q PONG; then
echo "[INIT] Redis is up! (took ${i} attempts)"
break
fi
if [ $i -eq $REDIS_WAIT_ATTEMPTS ]; then
echo "[INIT] ERROR: Redis failed to start within ${REDIS_WAIT_ATTEMPTS} seconds"
exit 1
fi
sleep 1
done
# Check if database needs initialization
echo "[INIT] Checking database initialization..."
if ! mysql --socket=/run/mysqld/mysqld.sock -u root -p"${MYSQL_ROOT_PASSWORD}" -e "SELECT 1" &>/dev/null; then
echo "[INIT] Setting root password for first-time setup..."
mysqladmin --socket=/run/mysqld/mysqld.sock -u root password "${MYSQL_ROOT_PASSWORD}"
fi
# Create database and user if they don't exist
echo "[INIT] Ensuring database and user exist..."
# Escape single quotes in password for SQL safety
DB_PASSWORD_ESCAPED=$(echo "${DB_PASSWORD}" | sed "s/'/''/g")
mysql --socket=/run/mysqld/mysqld.sock -u root -p"${MYSQL_ROOT_PASSWORD}" <<EOSQL
CREATE DATABASE IF NOT EXISTS \`${DB_DATABASE}\`;
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'localhost' IDENTIFIED BY '${DB_PASSWORD_ESCAPED}';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON \`${DB_DATABASE}\`.* TO '${DB_USERNAME}'@'localhost';
FLUSH PRIVILEGES;
EOSQL
echo "[INIT] Database setup verified"
# Fix permissions for persistent volumes
echo "[INIT] Fixing permissions..."
chown -R redis:redis /data/redis 2>/dev/null || true
chown -R appuser:appuser /var/www/html/storage 2>/dev/null || true
chmod -R 775 /var/www/html/storage 2>/dev/null || true
# Run Laravel migrations
echo "[INIT] Running Laravel migrations..."
cd /var/www/html
php artisan migrate --force
# Ensure storage link exists
if [ ! -L /var/www/html/public/storage ]; then
echo "[INIT] Creating storage link..."
php artisan storage:link
fi
# Stop background services gracefully (supervisor will manage them)
echo "[INIT] Stopping temporary database services..."
# Stop Redis first (faster shutdown)
echo "[INIT] Stopping temporary Redis..."
redis-cli shutdown 2>/dev/null || true
# Gracefully stop MariaDB
echo "[INIT] Stopping temporary MariaDB..."
mysqladmin --socket=/run/mysqld/mysqld.sock -u root -p"${MYSQL_ROOT_PASSWORD}" shutdown 2>/dev/null || kill $MYSQL_PID 2>/dev/null || true
# Wait for processes to actually terminate
echo "[INIT] Waiting for services to fully stop..."
for i in {1..15}; do
if ! pgrep -x mysqld >/dev/null && ! pgrep -x redis-server >/dev/null; then
echo "[INIT] All temporary services stopped successfully"
break
fi
if [ $i -eq 15 ]; then
echo "[INIT] WARNING: Services took too long to stop, forcing..."
pkill -9 mysqld || true
pkill -9 redis-server || true
sleep 1
fi
sleep 1
done
echo "========================================="
echo "[INIT] Initialization complete! Starting services..."
echo "========================================="
# Execute the main command (supervisord)
exec "$@"

View file

@ -0,0 +1,100 @@
server {
listen 80;
server_name _;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Disable server tokens
server_tokens off;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
# API Backend
location /api {
root /var/www/html/public;
try_files $uri $uri/ /index.php?$query_string;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 16k;
fastcgi_read_timeout 240;
}
}
# Sanctum/CSRF cookie endpoint
location /sanctum/csrf-cookie {
root /var/www/html/public;
try_files $uri /index.php?$query_string;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
# Storage files (user uploads, etc)
location /storage {
alias /var/www/html/storage/app/public;
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Frontend SPA
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# Health check endpoints
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Laravel health check endpoint
location /up {
root /var/www/html/public;
try_files $uri /index.php?$query_string;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
# Deny access to hidden files
location ~ /\.(?!well-known).* {
deny all;
}
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
}

559
docker/prod/README.md Normal file
View file

@ -0,0 +1,559 @@
# Trip Planner - Production Environment
This document describes the production Docker setup for Trip Planner.
## Overview
The production environment uses a **single all-in-one container** that includes:
- ✅ Frontend (React SPA built and served by Nginx)
- ✅ Backend (Laravel API with PHP-FPM)
- ✅ Database (MariaDB)
- ✅ Cache/Sessions (Redis)
- ✅ Web Server (Nginx as reverse proxy)
All services are managed by **Supervisord** within a single container.
## Architecture
```
┌─────────────────────────────────────────────────┐
│ trip-planner-production (Single Container) │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ Nginx (Port 80) │ │
│ │ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Frontend │ │ Backend API │ │ │
│ │ │ Static Files│ │ PHP-FPM:9000│ │ │
│ │ │ (/) │ │ (/api/*) │ │ │
│ │ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ MariaDB │ │ Redis │ │
│ │ localhost │ │ localhost │ │
│ │ :3306 │ │ :6379 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Managed by Supervisord │
└─────────────────────────────────────────────────┘
└─ Port 80 (or configured APP_PORT)
```
## Key Features
### Security
- ✅ Non-root users for services where possible
- ✅ Minimal Alpine-based image
- ✅ Database and Redis bound to localhost only
- ✅ Security headers configured
- ✅ OPcache enabled with production settings
- ✅ PHP display_errors disabled
### Optimization
- ✅ Multi-stage build (smaller image size ~800MB)
- ✅ OPcache with no timestamp validation
- ✅ Gzip compression enabled
- ✅ Static asset caching (1 year)
- ✅ Optimized Composer autoloader
### Reliability
- ✅ Health checks configured
- ✅ Automatic service restart via Supervisord
- ✅ Persistent data volumes for database, redis, and storage
- ✅ Proper initialization and migration on startup
## Building the Image
### Locally
```bash
# From project root
docker build -f Dockerfile.prod -t trip-planner:latest .
# Check image size
docker images trip-planner:latest
```
### Using Docker Compose
```bash
docker compose -f docker-compose.prod.yml build
```
### CI/CD (Automatic)
The image is automatically built and pushed to Codeberg Container Registry when:
- Changes are merged to `main` branch
- Pipeline extracts version from merge commit (e.g., from `release/v0.1.0`)
- Tagged as both `latest` and version number (e.g., `0.1.0`)
## Running the Container
### Using Docker Compose (Recommended)
```bash
# Start the container
docker compose -f docker-compose.prod.yml up -d
# View logs
docker compose -f docker-compose.prod.yml logs -f
# Stop the container
docker compose -f docker-compose.prod.yml down
# Stop and remove volumes (⚠️ deletes data!)
docker compose -f docker-compose.prod.yml down -v
```
### Using Docker Run
```bash
docker run -d \
--name trip-planner \
-p 8080:80 \
-e APP_KEY=base64:your-key-here \
-e DB_PASSWORD=secure-password \
-e DB_USERNAME=trip_user \
-e DB_DATABASE=trip_planner \
-v trip-planner-db:/var/lib/mysql \
-v trip-planner-redis:/data/redis \
-v trip-planner-storage:/var/www/html/storage/app \
trip-planner:latest
```
### Using the Published Image
```bash
# Pull from Codeberg Container Registry
docker pull codeberg.org/lvl0/trip-planner:latest
# Or a specific version
docker pull codeberg.org/lvl0/trip-planner:0.1.0
# Run it
docker run -d \
--name trip-planner \
-p 8080:80 \
-e APP_KEY=base64:your-key-here \
-e DB_PASSWORD=secure-password \
codeberg.org/lvl0/trip-planner:latest
```
## Environment Variables
### Required Variables
```env
# Application Key (generate with: php artisan key:generate)
APP_KEY=base64:your-generated-key-here
# Database Credentials
DB_PASSWORD=your-secure-password
DB_USERNAME=trip_user
DB_DATABASE=trip_planner
```
### Optional Variables (with defaults)
```env
# Application
APP_NAME="Trip Planner"
APP_ENV=production
APP_DEBUG=false
APP_URL=http://localhost:8080
# Database (internal MariaDB)
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
# Redis (internal)
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=null
# Cache & Session
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120
# Mail
MAIL_MAILER=log
MAIL_FROM_ADDRESS=noreply@tripplanner.local
MAIL_FROM_NAME="Trip Planner"
# Ports
APP_PORT=8080 # External port to expose
```
### Setting Environment Variables
**Docker Compose (recommended):**
```yaml
# Create .env file in project root
APP_KEY=base64:...
DB_PASSWORD=secret
APP_URL=https://tripplanner.example.com
```
**Docker Run:**
```bash
docker run -e APP_KEY=base64:... -e DB_PASSWORD=secret ...
```
## Persistent Data
The production setup uses three volumes for persistent data:
| Volume | Purpose | Path in Container |
|--------|---------|-------------------|
| `db-data` | MariaDB database files | `/var/lib/mysql` |
| `redis-data` | Redis persistence | `/data/redis` |
| `storage-data` | User uploads, files | `/var/www/html/storage/app` |
### Backup
```bash
# Backup database
docker exec trip-planner-production mysqldump -u trip_user -p trip_planner > backup.sql
# Backup volumes
docker run --rm \
-v trip-planner-db-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/db-backup.tar.gz /data
# Backup uploaded files
docker run --rm \
-v trip-planner-storage-data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/storage-backup.tar.gz /data
```
### Restore
```bash
# Restore database
docker exec -i trip-planner-production mysql -u trip_user -p trip_planner < backup.sql
# Restore volumes
docker run --rm \
-v trip-planner-db-data:/data \
-v $(pwd):/backup \
alpine sh -c "cd / && tar xzf /backup/db-backup.tar.gz"
```
## Health Checks
The container includes a health check endpoint:
```bash
# Check container health
docker inspect trip-planner-production | grep -A 5 Health
# Manual health check
curl http://localhost:8080/health
# Should return: healthy
# Check specific services
docker exec trip-planner-production supervisorctl status
```
## Accessing Services
When the container is running:
- **Application**: http://localhost:8080 (or your configured `APP_PORT`)
- **Health Check**: http://localhost:8080/health
- **API**: http://localhost:8080/api/*
### Internal Services (not exposed)
These services run inside the container and are not accessible from outside:
- MariaDB: `127.0.0.1:3306`
- Redis: `127.0.0.1:6379`
- PHP-FPM: `127.0.0.1:9000`
## Maintenance
### View Logs
```bash
# All services
docker compose -f docker-compose.prod.yml logs -f
# Specific service logs via supervisord
docker exec trip-planner-production supervisorctl tail -f nginx
docker exec trip-planner-production supervisorctl tail -f php-fpm
docker exec trip-planner-production supervisorctl tail -f mariadb
# Laravel logs
docker exec trip-planner-production tail -f /var/www/html/storage/logs/laravel.log
```
### Execute Commands
```bash
# Laravel Artisan
docker exec trip-planner-production php artisan <command>
# Examples:
docker exec trip-planner-production php artisan migrate:status
docker exec trip-planner-production php artisan cache:clear
docker exec trip-planner-production php artisan queue:work # Run queue worker
# Database access
docker exec -it trip-planner-production mysql -u trip_user -p trip_planner
# Shell access
docker exec -it trip-planner-production sh
```
### Update Application
```bash
# Pull latest image
docker pull codeberg.org/lvl0/trip-planner:latest
# Recreate container (preserves volumes)
docker compose -f docker-compose.prod.yml up -d --force-recreate
# Or specific version
docker pull codeberg.org/lvl0/trip-planner:0.2.0
docker compose -f docker-compose.prod.yml up -d
```
## Deployment
### On a VPS/Server
1. **Install Docker**:
```bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
```
2. **Create deployment directory**:
```bash
mkdir -p ~/trip-planner
cd ~/trip-planner
```
3. **Create docker-compose.yml**:
```yaml
version: '3.8'
services:
app:
image: codeberg.org/lvl0/trip-planner:latest
container_name: trip-planner
ports:
- "8080:80"
environment:
APP_KEY: ${APP_KEY}
DB_PASSWORD: ${DB_PASSWORD}
APP_URL: https://your-domain.com
volumes:
- db-data:/var/lib/mysql
- redis-data:/data/redis
- storage-data:/var/www/html/storage/app
restart: unless-stopped
volumes:
db-data:
redis-data:
storage-data:
```
4. **Create .env file**:
```bash
echo "APP_KEY=base64:$(openssl rand -base64 32)" > .env
echo "DB_PASSWORD=$(openssl rand -base64 24)" >> .env
```
5. **Start the application**:
```bash
docker compose up -d
```
6. **Set up reverse proxy** (optional, recommended):
Use Nginx, Caddy, or Traefik to handle HTTPS.
### With Reverse Proxy (Nginx Example)
```nginx
server {
listen 80;
server_name tripplanner.example.com;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
Then use Certbot for HTTPS:
```bash
sudo certbot --nginx -d tripplanner.example.com
```
## Troubleshooting
### Container won't start
**Check logs:**
```bash
docker compose -f docker-compose.prod.yml logs
```
**Common issues:**
- Missing `APP_KEY`: Generate one with `php artisan key:generate`
- Port already in use: Change `APP_PORT` in docker-compose
- Insufficient memory: Allocate at least 1GB RAM
### Database initialization fails
**Manually initialize:**
```bash
docker exec -it trip-planner-production sh
mysql_install_db --user=mysql --datadir=/var/lib/mysql
```
### Services not responding
**Check Supervisord status:**
```bash
docker exec trip-planner-production supervisorctl status
```
**Restart a service:**
```bash
docker exec trip-planner-production supervisorctl restart nginx
docker exec trip-planner-production supervisorctl restart php-fpm
```
### Permission errors
**Fix storage permissions:**
```bash
docker exec trip-planner-production chown -R appuser:appuser /var/www/html/storage
docker exec trip-planner-production chmod -R 775 /var/www/html/storage
```
### Health check failing
**Test manually:**
```bash
docker exec trip-planner-production curl -f http://localhost/health
# Check individual services
docker exec trip-planner-production supervisorctl status
```
### Performance issues
**Check resource usage:**
```bash
docker stats trip-planner-production
# Allocate more resources if needed (docker-compose)
# Add under 'app' service:
# deploy:
# resources:
# limits:
# memory: 2G
```
## Testing Production Locally
To test the production setup alongside your dev environment:
```bash
# Production runs on port 8080 (default)
docker compose -f docker-compose.prod.yml up -d
# Dev runs on separate ports (5173, 8000, etc.)
docker compose -f docker-compose.dev.yml up -d
# Both can run simultaneously without conflicts
```
Access:
- Production: http://localhost:8080
- Development: http://localhost:5173
## Security Considerations
### Securing the Production Deployment
1. **Change default passwords** in `.env`
2. **Use strong APP_KEY** (generate with `php artisan key:generate`)
3. **Enable HTTPS** with a reverse proxy
4. **Firewall rules**: Only expose necessary ports
5. **Regular updates**: Pull latest images regularly
6. **Monitor logs**: Set up log aggregation
7. **Backup regularly**: Automate volume backups
### Environment Variable Security
**Never commit secrets to git!**
```bash
# .env files are gitignored
# Use a secrets manager for production
# Or use Docker secrets/Kubernetes secrets
```
## Performance Tips
- The image includes OPcache with aggressive caching
- Static assets are cached for 1 year
- Gzip compression is enabled
- Redis handles sessions and cache
- Database is optimized for InnoDB
For high-traffic scenarios:
- Run multiple container replicas behind a load balancer
- Use external managed database (RDS, etc.)
- Use external Redis cluster
- Configure CDN for static assets
## Differences from Development
| Feature | Development | Production |
|---------|------------|------------|
| Containers | 5 separate | 1 all-in-one |
| Code | Live mounted | Baked into image |
| Frontend | Vite dev server | Pre-built static files |
| Debugging | Enabled | Disabled |
| Caching | Minimal | Aggressive |
| Security | Relaxed | Hardened |
| Size | ~2GB+ | ~800MB |
## CI/CD Integration
Images are automatically built via Woodpecker CI on Codeberg:
```yaml
# .woodpecker.yml extracts version from merge commits
# Example: merging release/v0.1.0 → tags 0.1.0 and latest
```
**Registry**: `codeberg.org/lvl0/trip-planner`
**Tags**:
- `latest`: Most recent build from main
- `0.1.0`, `0.2.0`, etc.: Version tags
## Need Help?
- Check the main [docker/README.md](../README.md)
- Review container logs: `docker logs trip-planner-production`
- Check service status: `docker exec trip-planner-production supervisorctl status`
- Inspect health: `docker inspect trip-planner-production`

66
docker/supervisord.conf Normal file
View file

@ -0,0 +1,66 @@
[unix_http_server]
file=/var/run/supervisor.sock
chmod=0700
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
loglevel=info
childlogdir=/var/log/supervisor
[program:mariadb]
command=/usr/bin/mysqld --user=mysql --datadir=/var/lib/mysql --bind-address=127.0.0.1 --port=3306
autostart=true
autorestart=true
priority=10
stopwaitsecs=30
stopsignal=TERM
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
user=mysql
[program:redis]
command=/usr/bin/redis-server --bind 127.0.0.1 --port 6379 --dir /data/redis --appendonly yes --logfile ""
autostart=true
autorestart=true
priority=20
stopwaitsecs=10
stopsignal=TERM
stdout_logfile=/var/log/supervisor/redis.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/log/supervisor/redis-error.log
stderr_logfile_maxbytes=10MB
[program:php-fpm]
command=/usr/local/sbin/php-fpm -F
autostart=true
autorestart=true
priority=30
stopwaitsecs=10
stopsignal=QUIT
stdout_logfile=/var/log/supervisor/php-fpm.log
stdout_logfile_maxbytes=10MB
stderr_logfile=/var/log/supervisor/php-fpm-error.log
stderr_logfile_maxbytes=10MB
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
priority=40
stopwaitsecs=10
stopsignal=QUIT
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

51
frontend/.dockerignore Normal file
View file

@ -0,0 +1,51 @@
# Dependencies
node_modules/
package-lock.json
yarn.lock
pnpm-lock.yaml
# Development
.env
.env.*
!.env.example
.env.local
.env.development
# Build artifacts (will be rebuilt)
dist/
build/
.vite/
# Testing
coverage/
.nyc_output/
# Development files
.git/
.github/
.gitignore
.editorconfig
.eslintrc*
.prettierrc*
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Documentation
README.md
CHANGELOG.md
LICENSE
# Docker files
Dockerfile*
docker-compose*.yml
.dockerignore
# Logs
logs/
*.log
npm-debug.log*