diff --git a/.env.docker.example b/.env.docker.example new file mode 100644 index 00000000000..30fd46c06aa --- /dev/null +++ b/.env.docker.example @@ -0,0 +1,72 @@ +APP_NAME=Cachet +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_TIMEZONE=UTC +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=db +DB_PORT=3306 +DB_DATABASE=cachet +DB_USERNAME=cachet +DB_PASSWORD=your_secure_db_password + +SESSION_DRIVER=redis +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=redis + +CACHE_STORE=redis +CACHE_PREFIX= + +REDIS_CLIENT=predis +REDIS_HOST=redis +REDIS_PASSWORD=your_secure_redis_password +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" + +CACHET_BEACON=false +CACHET_EMOJI=false +CACHET_AUTO_TWITTER=true +CACHET_PATH=/ +CACHET_TRUSTED_PROXIES="" + +NIGHTWATCH_ENABLED=false + +# Docker environment variables +MYSQL_ROOT_PASSWORD=your_mysql_root_password \ No newline at end of file diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 00000000000..ab8d2e3f9a4 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,181 @@ +# Cachet Docker Setup + +This guide explains how to run Cachet using Docker with a complete setup including MySQL database and Redis cache. + +## Quick Start + +1. **Clone the repository** + ```bash + git clone https://github.com/cachethq/cachet.git + cd cachet + ``` + +2. **Create environment file** + ```bash + cp .env.docker.example .env + ``` + +3. **Update environment variables** + Edit `.env` file and update the following variables: + - `APP_KEY`: Generate using `docker-compose exec app php artisan key:generate --show` after starting containers + - `DB_PASSWORD`: Set a secure database password + - `REDIS_PASSWORD`: Set a secure Redis password + - `MYSQL_ROOT_PASSWORD`: Set a secure MySQL root password + - `APP_URL`: Set to your domain (e.g., `https://status.yourdomain.com`) + +4. **Build and start services** + ```bash + docker-compose up -d --build + ``` + +5. **Generate application key** + ```bash + # Generate and set the APP_KEY + docker-compose exec app php artisan key:generate + ``` + +6. **Access Cachet** + Open your browser and navigate to `http://localhost` (or your configured domain) + +## Architecture + +The Docker setup includes three services: + +- **app**: Cachet application with Nginx + PHP-FPM +- **db**: MySQL 8.0 database +- **redis**: Redis 7 for caching and sessions + +## Configuration Files + +### Docker Configuration +- `Dockerfile`: Multi-stage build for production deployment +- `docker-compose.yml`: Service orchestration +- `docker/nginx/nginx.conf`: Nginx configuration for Laravel +- `docker/php/cachet.ini`: PHP configuration optimizations +- `docker/supervisor/supervisord.conf`: Process manager configuration +- `docker/entrypoint.sh`: Startup script with database migrations + +### Environment Configuration +- `.env.docker.example`: Template environment file for Docker +- Copy to `.env` and customize for your deployment + +## Production Deployment + +### Docker Hub Image (Recommended) + +You can use the pre-built image from Docker Hub: + +```yaml +services: + app: + image: cachet/cachet:latest + # ... rest of configuration +``` + +### Building Your Own Image + +1. **Build the image** + ```bash + docker build -t your-registry/cachet:latest . + ``` + +2. **Push to registry** + ```bash + docker push your-registry/cachet:latest + ``` + +3. **Update docker-compose.yml** + ```yaml + services: + app: + image: your-registry/cachet:latest + ``` + +## Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `APP_KEY` | Laravel application key | *Required* | +| `APP_URL` | Application URL | `http://localhost` | +| `DB_PASSWORD` | Database password | *Required* | +| `REDIS_PASSWORD` | Redis password | *Required* | +| `MYSQL_ROOT_PASSWORD` | MySQL root password | *Required* | + +## Data Persistence + +The setup includes persistent volumes for: +- MySQL data: `mysql_data` +- Redis data: `redis_data` +- Application storage: `storage_data` + +## Health Checks + +Both MySQL and Redis services include health checks to ensure the application starts only when dependencies are ready. + +## Scaling + +To scale the application: + +```bash +docker-compose up -d --scale app=3 +``` + +Add a load balancer like Nginx or Traefik in front of the application containers. + +## Troubleshooting + +### Database Connection Issues +```bash +# Check database logs +docker-compose logs db + +# Test database connection +docker-compose exec app php artisan tinker --execute="DB::connection()->getPdo();" +``` + +### Redis Connection Issues +```bash +# Check Redis logs +docker-compose logs redis + +# Test Redis connection +docker-compose exec redis redis-cli auth your_redis_password ping +``` + +### Application Logs +```bash +# View application logs +docker-compose logs app + +# Follow logs in real-time +docker-compose logs -f app +``` + +### Reset Everything +```bash +# Stop and remove all containers and volumes +docker-compose down -v + +# Rebuild and start +docker-compose up -d --build +``` + +## Security Considerations + +1. **Change default passwords**: Always update database and Redis passwords +2. **Use HTTPS**: Configure SSL/TLS certificates for production +3. **Firewall**: Restrict access to database and Redis ports +4. **Updates**: Regularly update Docker images and dependencies + +## Development + +For development with live code reloading: + +```bash +# Mount source code as volume +docker-compose -f docker-compose.dev.yml up -d +``` + +## Support + +For issues related to Docker setup, please open an issue on the [Cachet repository](https://github.com/cachethq/cachet/issues). \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..5b2758825ed --- /dev/null +++ b/Dockerfile @@ -0,0 +1,50 @@ +FROM php:8.3-fpm + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + nginx \ + curl \ + zip \ + unzip \ + git \ + supervisor \ + libicu-dev \ + libzip-dev \ + && docker-php-ext-install \ + pdo \ + pdo_mysql \ + zip \ + intl \ + opcache \ + && pecl install redis \ + && docker-php-ext-enable redis \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Composer +COPY --from=composer:2.7 /usr/bin/composer /usr/bin/composer + +# Copy configuration files +COPY docker/php/cachet.ini /usr/local/etc/php/conf.d/cachet.ini +COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf +COPY docker/supervisor/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY docker/entrypoint.sh /entrypoint.sh + +# Set working directory +WORKDIR /var/www/html + +# Copy application code +COPY . . + +# Install PHP dependencies including Redis client +RUN COMPOSER_ALLOW_SUPERUSER=1 composer require predis/predis --no-scripts \ + && composer install --no-dev --optimize-autoloader --no-scripts + +# Set permissions +RUN chown -R www-data:www-data /var/www/html \ + && chmod -R 755 storage bootstrap/cache \ + && chmod +x artisan /entrypoint.sh + +EXPOSE 80 + +CMD ["/entrypoint.sh"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000000..edb155c1409 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,72 @@ +services: + app: + build: . + ports: + - "80:80" + environment: + - APP_ENV=production + - APP_KEY=${APP_KEY} + - APP_DEBUG=false + - APP_URL=http://localhost + - DB_CONNECTION=mysql + - DB_HOST=db + - DB_PORT=3306 + - DB_DATABASE=cachet + - DB_USERNAME=cachet + - DB_PASSWORD=${DB_PASSWORD} + - CACHE_STORE=redis + - SESSION_DRIVER=redis + - QUEUE_CONNECTION=redis + - REDIS_CLIENT=predis + - REDIS_HOST=redis + - REDIS_PASSWORD=${REDIS_PASSWORD} + - REDIS_PORT=6379 + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - storage_data:/var/www/html/storage + restart: unless-stopped + networks: + - cachet + + db: + image: mysql:8.0 + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} + - MYSQL_DATABASE=cachet + - MYSQL_USER=cachet + - MYSQL_PASSWORD=${DB_PASSWORD} + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 20s + retries: 10 + restart: unless-stopped + networks: + - cachet + + redis: + image: redis:7-alpine + command: redis-server --requirepass ${REDIS_PASSWORD} + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "auth", "${REDIS_PASSWORD}", "ping"] + timeout: 3s + retries: 5 + restart: unless-stopped + networks: + - cachet + +volumes: + mysql_data: + redis_data: + storage_data: + +networks: + cachet: + driver: bridge \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000000..1182fa3ca01 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/sh +set -e + +# Wait for database connection +echo "Waiting for database..." +for i in $(seq 1 30); do + if php artisan tinker --execute="DB::connection()->getPdo();" >/dev/null 2>&1; then + echo "Database connected!" + break + fi + echo "Database not ready, waiting... ($i/30)" + sleep 2 +done + +# Run migrations +echo "Running migrations..." +php artisan migrate --force + +# Cache configuration +echo "Caching configuration..." +php artisan config:cache +php artisan route:cache + +# Start supervisor +echo "Starting services..." +exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf \ No newline at end of file diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 00000000000..299fa2a15a0 --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,52 @@ +user www-data; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + 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 /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + server { + listen 80; + server_name _; + root /var/www/html/public; + index index.php index.html; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?)$ { + expires max; + access_log off; + } + + location ~ /\.ht { + deny all; + } + } +} \ No newline at end of file diff --git a/docker/php/cachet.ini b/docker/php/cachet.ini new file mode 100644 index 00000000000..19339da312a --- /dev/null +++ b/docker/php/cachet.ini @@ -0,0 +1,10 @@ +memory_limit = 256M +upload_max_filesize = 100M +post_max_size = 100M +max_execution_time = 300 +opcache.enable = 1 +opcache.memory_consumption = 128 +opcache.interned_strings_buffer = 8 +opcache.max_accelerated_files = 4000 +opcache.revalidate_freq = 2 +opcache.fast_shutdown = 1 \ No newline at end of file diff --git a/docker/supervisor/supervisord.conf b/docker/supervisor/supervisord.conf new file mode 100644 index 00000000000..4a1467fbb82 --- /dev/null +++ b/docker/supervisor/supervisord.conf @@ -0,0 +1,34 @@ +[supervisord] +nodaemon=true +user=root +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:nginx] +command=nginx -g "daemon off;" +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=0 + +[program:php-fpm] +command=php-fpm --nodaemonize +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=0 + +[program:queue-worker] +command=php artisan queue:work --sleep=3 --tries=3 --max-time=3600 +directory=/var/www/html +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=true +startretries=0 +user=www-data \ No newline at end of file