2 - add production build and compose files

This commit is contained in:
myrmidex 2025-11-15 03:23:35 +01:00
parent afa4cf27b7
commit ed27f4bb49
11 changed files with 1143 additions and 7 deletions

View file

@ -0,0 +1,109 @@
# Development Docker Setup
This directory contains Docker Compose files for local development using Laravel Sail.
## Files
- **docker-compose.yml** - Base Sail configuration for backend (Laravel) and MySQL
- **docker-compose.override.yml** - Development overrides:
- Podman/SELinux compatibility (`:Z` volume flags)
- Standard MySQL image (instead of mysql-server)
- Frontend container (Node.js development server)
## Quick Start
Use the automated setup script from the project root:
```bash
./bin/start-dev
```
This script will:
1. Create `.env` if it doesn't exist
2. Install backend dependencies (composer)
3. Start all containers (backend, MySQL, frontend)
4. Run database migrations
5. Optionally seed the database
## Manual Usage
### Start all services
```bash
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
up -d
```
### View logs
```bash
# All services
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
logs -f
# Specific service
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
logs -f frontend
```
### Run backend commands
```bash
# Run migrations
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
exec backend php artisan migrate
# Run tinker
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
exec backend php artisan tinker
# Run tests
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
exec backend php artisan test
```
### Stop all services
```bash
docker compose -f .docker/development/docker-compose.yml \
-f .docker/development/docker-compose.override.yml \
down
```
## Services
| Service | Port | Description |
|---------|------|-------------|
| **backend** | 8000 | Laravel API (PHP 8.4 + Sail) |
| **mysql** | 3306 | MySQL 8.0 database |
| **frontend** | 5173 | Vite dev server (React Router) |
## Environment Variables
The backend reads from `backend/.env`. Key variables:
- `APP_PORT` - Backend port (default: 80, set to 8000 for Podman)
- `DB_*` - Database connection settings
- `FORWARD_DB_PORT` - Exposed MySQL port (default: 3306)
## Volume Mounts
- `backend/``/var/www/html` (backend container)
- `frontend/``/app` (frontend container)
- MySQL data persists in named volume `sail-mysql`
## Podman Compatibility
The `:Z` flag on volumes enables SELinux compatibility for Podman users. This is automatically included in the override file.
## Notes
- The frontend container automatically runs `npm install` and `npm run dev` on startup
- Laravel Sail handles PHP-FPM, supervisor, and other backend services
- Hot reload is enabled for both frontend (Vite HMR) and backend (file watchers)

View file

@ -0,0 +1,25 @@
services:
backend:
volumes:
- '../../backend:/var/www/html:Z'
mysql:
image: 'docker.io/library/mysql:8.0'
environment:
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
volumes:
- 'sail-mysql:/var/lib/mysql:Z'
frontend:
image: 'docker.io/library/node:22-alpine'
working_dir: /app
command: sh -c "npm install && npm run dev -- --host"
volumes:
- '../../frontend:/app:Z'
ports:
- '5173:5173'
networks:
- sail
depends_on:
- backend

View file

@ -0,0 +1,54 @@
services:
backend:
build:
context: '../../backend/vendor/laravel/sail/runtimes/8.4'
dockerfile: Dockerfile
args:
WWWGROUP: '${WWWGROUP}'
image: 'sail-8.4/app'
extra_hosts:
- 'host.docker.internal:host-gateway'
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
environment:
WWWUSER: '${WWWUSER}'
LARAVEL_SAIL: 1
XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
IGNITION_LOCAL_SITES_PATH: '${PWD}'
volumes:
- '../../backend:/var/www/html:Z'
networks:
- sail
depends_on:
- mysql
mysql:
image: 'docker.io/mysql/mysql-server:8.0'
ports:
- '${FORWARD_DB_PORT:-3306}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
MYSQL_ROOT_HOST: '%'
MYSQL_DATABASE: '${DB_DATABASE}'
MYSQL_USER: '${DB_USERNAME}'
MYSQL_PASSWORD: '${DB_PASSWORD}'
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
- 'sail-mysql:/var/lib/mysql:Z'
networks:
- sail
healthcheck:
test:
- CMD
- mysqladmin
- ping
- '-p${DB_PASSWORD}'
retries: 3
timeout: 5s
networks:
sail:
driver: bridge
volumes:
sail-mysql:
driver: local

View file

@ -0,0 +1,29 @@
# Ignore .env files - they should be created at runtime
backend/.env
backend/.env.production
# Ignore node_modules - we install dependencies during build
frontend/node_modules
backend/vendor
# Ignore git
.git
.gitignore
# Ignore build artifacts
frontend/build
backend/bootstrap/cache/*
backend/storage/logs/*
backend/storage/framework/cache/*
backend/storage/framework/sessions/*
backend/storage/framework/views/*
# Ignore test files
backend/tests
frontend/tests
# Ignore development files
.vscode
.idea
*.log
.DS_Store

View file

@ -0,0 +1,151 @@
# ============================================
# Dish Planner - All-in-One Production Image
# ============================================
# This Dockerfile creates a single container with:
# - MySQL 8.0 database
# - PHP 8.2-FPM (Laravel backend)
# - Node.js 20 (React Router frontend)
# - Nginx (reverse proxy)
# - Supervisor (process manager)
#
# Build: docker build -f .docker/production/Dockerfile -t codeberg.org/lvl0/dish-planner:latest .
# Deploy: See .docker/production/README.md for deployment instructions
# ============================================
FROM ubuntu:22.04
# Prevent interactive prompts during build
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=UTC
# ============================================
# Install System Dependencies
# ============================================
RUN apt-get update && apt-get install -y \
# Basic utilities
curl \
wget \
git \
unzip \
ca-certificates \
gnupg \
supervisor \
software-properties-common \
# Nginx
nginx \
# MySQL Server
mysql-server \
&& rm -rf /var/lib/apt/lists/*
# ============================================
# Install PHP 8.2 from ondrej/php PPA
# ============================================
RUN add-apt-repository ppa:ondrej/php -y \
&& apt-get update \
&& apt-get install -y \
php8.2-fpm \
php8.2-cli \
php8.2-mysql \
php8.2-mbstring \
php8.2-xml \
php8.2-bcmath \
php8.2-curl \
php8.2-zip \
php8.2-gd \
php8.2-intl \
&& rm -rf /var/lib/apt/lists/*
# ============================================
# Install Node.js 20
# ============================================
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
# ============================================
# Install Composer
# ============================================
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
# ============================================
# Set up working directories
# ============================================
WORKDIR /var/www
# Create necessary directories
RUN mkdir -p /var/www/backend \
/var/www/frontend \
/var/www/storage/logs \
/var/lib/mysql \
/run/php
# ============================================
# Copy and Build Backend (Laravel)
# ============================================
COPY backend /var/www/backend
WORKDIR /var/www/backend
# Remove any existing .env files - they will be created at runtime
RUN rm -f .env .env.production
# Install PHP dependencies (production)
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Set permissions for Laravel
RUN chown -R www-data:www-data /var/www/backend/storage /var/www/backend/bootstrap/cache \
&& chmod -R 775 /var/www/backend/storage /var/www/backend/bootstrap/cache
# Add www-data to mysql group so it can access MySQL socket
RUN usermod -a -G mysql www-data
# ============================================
# Copy and Build Frontend (React Router)
# ============================================
COPY frontend /var/www/frontend
WORKDIR /var/www/frontend
# Install all dependencies (including dev for build), build, then remove dev deps
RUN npm ci \
&& npm run build \
&& npm prune --production
# ============================================
# Configure Nginx
# ============================================
COPY .docker/production/nginx.conf /etc/nginx/sites-available/default
RUN ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default \
&& rm -f /etc/nginx/sites-enabled/default.old
# ============================================
# Configure PHP-FPM
# ============================================
RUN sed -i 's/listen = \/run\/php\/php8.2-fpm.sock/listen = 127.0.0.1:9000/' /etc/php/8.2/fpm/pool.d/www.conf
# ============================================
# Configure MySQL
# ============================================
# Allow MySQL to bind to all interfaces (for easier debugging if needed)
RUN sed -i 's/bind-address.*/bind-address = 127.0.0.1/' /etc/mysql/mysql.conf.d/mysqld.cnf || true
# ============================================
# Copy Configuration Files
# ============================================
COPY .docker/production/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY .docker/production/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# ============================================
# Create volume mount points
# ============================================
VOLUME ["/var/lib/mysql", "/var/www/backend/storage"]
# ============================================
# Expose HTTP port
# ============================================
EXPOSE 80
# ============================================
# Set entrypoint and default command
# ============================================
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

View file

@ -0,0 +1,345 @@
# Dish Planner - Self-Hosted Deployment Guide
This directory contains everything you need to deploy Dish Planner as a self-hosted all-in-one container.
> **Note**: This guide uses an embedded docker-compose configuration (no separate file to download). Simply copy the YAML configuration shown below and save it as `docker-compose.yml`.
## Quick Start
### For End Users (Using Pre-Built Image)
> **Image Versioning**: This guide uses the `:latest` tag for simplicity. For production deployments, consider using a specific version tag (e.g., `:v0.3`) for stability and predictable updates.
1. **Create a docker-compose.yml file:**
Copy the following configuration and save it as `docker-compose.yml`:
```yaml
# ============================================
# Dish Planner - Self-Hosted Deployment
# ============================================
#
# QUICK START:
# 1. Save this file as docker-compose.yml
# 2. (Optional) Edit the environment variables below
# 3. Run: docker compose up -d
# 4. Access your app at http://localhost:3000
#
# IMPORTANT SECURITY NOTES:
# - Database credentials are auto-generated on first run
# - Check the container logs to see generated credentials:
# docker logs dishplanner
# - For production, set a custom APP_URL environment variable
#
# CUSTOMIZATION:
# You can override any environment variable by uncommenting
# and editing the values below, or by creating a .env file.
#
# ============================================
services:
dishplanner:
# Pre-built all-in-one image from Codeberg Container Registry
image: codeberg.org/lvl0/dish-planner:latest
container_name: dishplanner
restart: unless-stopped
# ----------------------------------------
# Port Configuration
# ----------------------------------------
# The application will be accessible on port 3000
# Change the left number to use a different host port
# Example: "8080:80" to access on port 8080
ports:
- "3000:80"
# ----------------------------------------
# Environment Variables
# ----------------------------------------
# Uncomment and customize as needed
environment:
# Application URL (set this to your domain)
- APP_URL=http://localhost:3000
# Application environment (production recommended)
- APP_ENV=production
# Debug mode (set to false in production for security)
- APP_DEBUG=false
# Database configuration
# Note: Credentials are auto-generated on first run if not set
# Check logs for generated credentials: docker logs dishplanner
# - DB_DATABASE=dishplanner
# - DB_USERNAME=dishuser
# - DB_PASSWORD=change-this-secure-password
# Timezone (optional)
# - APP_TIMEZONE=UTC
# ----------------------------------------
# Persistent Data Volumes
# ----------------------------------------
# These volumes ensure data persists across container restarts
volumes:
# MySQL database data
- dishplanner_mysql_data:/var/lib/mysql
# Laravel storage (uploaded files, logs, cache)
- dishplanner_storage:/var/www/backend/storage
# Supervisor logs
- dishplanner_logs:/var/log/supervisor
# ----------------------------------------
# Health Check (optional)
# ----------------------------------------
# Uncomment to enable container health monitoring
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost/api/health"]
# interval: 30s
# timeout: 10s
# retries: 3
# start_period: 60s
# ----------------------------------------
# Named Volumes
# ----------------------------------------
# Docker manages these volumes - data persists even if container is removed
volumes:
dishplanner_mysql_data:
driver: local
dishplanner_storage:
driver: local
dishplanner_logs:
driver: local
# ----------------------------------------
# Network Configuration
# ----------------------------------------
# Uses default bridge network (suitable for single-host deployment)
# For advanced setups, you can define custom networks here
```
2. **Start the application:**
```bash
docker compose up -d
```
3. **View the initialization logs to get your database credentials:**
```bash
docker logs dishplanner
```
Save the auto-generated database credentials shown in the logs!
4. **Access the application:**
Open your browser to `http://localhost:3000`
That's it! The application is now running with:
- MySQL database
- Laravel backend API
- React Router frontend
- Nginx reverse proxy
- Background queue worker
### For DockGE Users
1. In the DockGE web UI, click "Add Stack"
2. Copy the entire YAML configuration from the code block above (starting from `services:` and including all volumes)
3. Paste it into the DockGE compose editor
4. Optionally edit the environment variables
5. Click "Start"
6. View logs to get auto-generated database credentials
## Configuration
### Environment Variables
You can customize the deployment by setting these environment variables in the docker-compose file:
| Variable | Default | Description |
|----------|---------|-------------|
| `APP_URL` | `http://localhost:3000` | The URL where your app is accessible |
| `APP_ENV` | `production` | Environment mode (production/local) |
| `APP_DEBUG` | `false` | Enable debug mode (never use in production!) |
| `DB_DATABASE` | `dishplanner` | Database name |
| `DB_USERNAME` | `dishuser` | Database username |
| `DB_PASSWORD` | Auto-generated | Database password (check logs for generated value) |
### Changing the Port
By default, the app runs on port 3000. To use a different port, edit the `ports` section:
```yaml
ports:
- "8080:80" # This makes the app available on port 8080
```
### Persistent Data
The following data is persisted in Docker volumes:
- **MySQL database** - All your dishes, schedules, and users
- **Laravel storage** - Uploaded files, logs, and cache
- **Supervisor logs** - Application and service logs
Even if you remove the container, this data remains intact.
## Building the Image Yourself
If you prefer to build the image yourself instead of using the pre-built one:
1. **Clone the repository:**
```bash
git clone https://codeberg.org/lvl0/dish-planner.git
cd dish-planner
```
2. **Build the image:**
```bash
docker build -f .docker/production/Dockerfile -t codeberg.org/lvl0/dish-planner:latest .
```
3. **Create a docker-compose.yml file** using the configuration shown in the Quick Start section above (or save the embedded compose config to a file)
4. **Run it:**
```bash
docker compose up -d
```
## Management
### Viewing Logs
```bash
# All logs
docker logs dishplanner
# Follow logs in real-time
docker logs -f dishplanner
# Last 100 lines
docker logs --tail 100 dishplanner
```
### Stopping the Application
```bash
docker compose down
```
### Restarting the Application
```bash
docker compose restart
```
### Updating to a New Version
1. Pull the latest image:
```bash
docker compose pull
```
2. Recreate the container:
```bash
docker compose up -d
```
3. Your data persists in volumes automatically!
## Troubleshooting
### "Cannot connect to database"
Check that MySQL started successfully:
```bash
docker logs dishplanner | grep mysql
```
### "502 Bad Gateway"
One of the services may not have started. Check supervisor logs:
```bash
docker exec dishplanner supervisorctl status
```
### Reset Everything (CAUTION: Deletes all data!)
```bash
docker compose down -v # The -v flag removes volumes
docker compose up -d
```
### Access the Container Shell
```bash
docker exec -it dishplanner bash
```
### Run Laravel Commands
```bash
# Run migrations
docker exec dishplanner php /var/www/backend/artisan migrate
# Create a new user (if needed)
docker exec -it dishplanner php /var/www/backend/artisan tinker
```
## Architecture
This all-in-one container includes:
- **MySQL 8.0** - Database server
- **PHP 8.2-FPM** - Runs the Laravel backend
- **Node.js 20** - Runs the React Router frontend
- **Nginx** - Web server and reverse proxy
- **Supervisord** - Process manager that keeps everything running
All services start automatically and are monitored by supervisord. If any service crashes, it will be automatically restarted.
## Security Recommendations
For production deployments:
1. **Set a strong database password:**
```yaml
environment:
- DB_PASSWORD=your-very-secure-password-here
```
2. **Use HTTPS:** Put the container behind a reverse proxy (Nginx, Caddy, Traefik) with SSL/TLS
3. **Set APP_DEBUG to false:**
```yaml
environment:
- APP_DEBUG=false
```
4. **Keep the image updated:** Regularly pull and deploy new versions
5. **Backup your data:**
```bash
# Backup volumes
docker run --rm -v dishplanner_mysql_data:/data -v $(pwd):/backup ubuntu tar czf /backup/mysql-backup.tar.gz /data
```
## Support
For issues and questions:
- Codeberg Issues: https://codeberg.org/lvl0/dish-planner/issues
- Documentation: https://codeberg.org/lvl0/dish-planner
## What's Inside
The container runs these processes (managed by supervisord):
1. **MySQL** - Database (port 3306, internal only)
2. **PHP-FPM** - Laravel application (port 9000, internal only)
3. **Node.js** - React frontend (port 3000, internal only)
4. **Nginx** - Reverse proxy (port 80, exposed)
5. **Queue Worker** - Background job processor
Only Nginx's port 80 is exposed to the host (mapped to 3000 by default).

View file

@ -0,0 +1,105 @@
# ============================================
# Dish Planner - Self-Hosted Deployment
# ============================================
#
# QUICK START:
# 1. Copy this file to your server
# 2. (Optional) Edit the environment variables below
# 3. Run: docker compose up -d
# 4. Access your app at http://localhost:3000
#
# IMPORTANT SECURITY NOTES:
# - Database credentials are auto-generated on first run
# - Check the container logs to see generated credentials:
# docker logs dishplanner
# - For production, set a custom APP_URL environment variable
#
# CUSTOMIZATION:
# You can override any environment variable by uncommenting
# and editing the values below, or by creating a .env file.
#
# ============================================
services:
dishplanner:
# Pre-built all-in-one image from Docker Hub
image: localhost/dishplanner-allinone:test
container_name: dishplanner
restart: unless-stopped
# ----------------------------------------
# Port Configuration
# ----------------------------------------
# The application will be accessible on port 3000
# Change the left number to use a different host port
# Example: "8080:80" to access on port 8080
ports:
- "3000:80"
# ----------------------------------------
# Environment Variables
# ----------------------------------------
# Uncomment and customize as needed
environment:
# Application URL (set this to your domain)
- APP_URL=http://localhost:3000
# Application environment (production recommended)
- APP_ENV=production
# Debug mode (set to false in production for security)
- APP_DEBUG=false
# Database configuration
# Note: Credentials are auto-generated on first run if not set
# Check logs for generated credentials: docker logs dishplanner
# - DB_DATABASE=dishplanner
# - DB_USERNAME=dishuser
# - DB_PASSWORD=change-this-secure-password
# Timezone (optional)
# - APP_TIMEZONE=UTC
# ----------------------------------------
# Persistent Data Volumes
# ----------------------------------------
# These volumes ensure data persists across container restarts
volumes:
# MySQL database data
- dishplanner_mysql_data:/var/lib/mysql
# Laravel storage (uploaded files, logs, cache)
- dishplanner_storage:/var/www/backend/storage
# Supervisor logs
- dishplanner_logs:/var/log/supervisor
# ----------------------------------------
# Health Check (optional)
# ----------------------------------------
# Uncomment to enable container health monitoring
# healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost/api/health"]
# interval: 30s
# timeout: 10s
# retries: 3
# start_period: 60s
# ----------------------------------------
# Named Volumes
# ----------------------------------------
# Docker manages these volumes - data persists even if container is removed
volumes:
dishplanner_mysql_data:
driver: local
dishplanner_storage:
driver: local
dishplanner_logs:
driver: local
# ----------------------------------------
# Network Configuration
# ----------------------------------------
# Uses default bridge network (suitable for single-host deployment)
# For advanced setups, you can define custom networks here

View file

@ -0,0 +1,153 @@
#!/bin/bash
set -e
echo "=========================================="
echo "Dish Planner - Starting Initialization"
echo "=========================================="
# ============================================
# MySQL Initialization
# ============================================
echo "[1/6] Initializing MySQL..."
# Check if MySQL data directory is empty (first run)
if [ ! -d "/var/lib/mysql/mysql" ]; then
echo " → First run detected, initializing MySQL data directory..."
mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql
fi
# Start MySQL temporarily in background for setup
echo " → Starting MySQL..."
mysqld --user=mysql --datadir=/var/lib/mysql &
MYSQL_PID=$!
# Wait for MySQL to be ready
echo " → Waiting for MySQL to be ready..."
for i in {1..30}; do
if mysqladmin ping -h localhost --silent; then
echo " → MySQL is ready!"
break
fi
echo " → Waiting... ($i/30)"
sleep 2
done
# ============================================
# Database Setup
# ============================================
echo "[2/6] Setting up database..."
# Set default values for database credentials
DB_DATABASE=${DB_DATABASE:-dishplanner}
DB_USERNAME=${DB_USERNAME:-dishuser}
# Check if this is first run by looking for credentials file
CREDS_FILE="/var/www/backend/storage/.db_credentials"
if [ -f "$CREDS_FILE" ]; then
# Not first run - load existing credentials
echo " → Loading existing database credentials..."
source "$CREDS_FILE"
else
# First run - generate new credentials and save them
echo " → First run detected, generating credentials..."
DB_PASSWORD=${DB_PASSWORD:-$(openssl rand -base64 32)}
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-$(openssl rand -base64 32)}
# Save credentials for future restarts
cat > "$CREDS_FILE" <<EOF
DB_PASSWORD='${DB_PASSWORD}'
MYSQL_ROOT_PASSWORD='${MYSQL_ROOT_PASSWORD}'
EOF
chmod 600 "$CREDS_FILE"
# Set root password
mysql -u root <<-EOSQL
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
FLUSH PRIVILEGES;
EOSQL
# Create database and user
mysql -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}';
CREATE USER IF NOT EXISTS '${DB_USERNAME}'@'%' IDENTIFIED BY '${DB_PASSWORD}';
GRANT ALL PRIVILEGES ON ${DB_DATABASE}.* TO '${DB_USERNAME}'@'localhost';
GRANT ALL PRIVILEGES ON ${DB_DATABASE}.* TO '${DB_USERNAME}'@'%';
FLUSH PRIVILEGES;
EOSQL
echo " → Database '${DB_DATABASE}' created"
echo " → User '${DB_USERNAME}' created"
fi
# ============================================
# Laravel Environment Setup
# ============================================
echo "[3/6] Configuring Laravel environment..."
cd /var/www/backend
# Create .env if it doesn't exist
if [ ! -f .env ]; then
echo " → Creating .env file..."
cp .env.example .env
fi
# Update database credentials in .env using a more robust method
# Use @ as delimiter to avoid conflicts with special chars in passwords
sed -i "s@DB_DATABASE=.*@DB_DATABASE=${DB_DATABASE}@" .env
sed -i "s@DB_USERNAME=.*@DB_USERNAME=${DB_USERNAME}@" .env
sed -i "s@DB_PASSWORD=.*@DB_PASSWORD=${DB_PASSWORD}@" .env
sed -i "s@DB_HOST=.*@DB_HOST=127.0.0.1@" .env
sed -i "s@DB_PORT=.*@DB_PORT=3306@" .env
# Generate APP_KEY if not set
if ! grep -q "APP_KEY=base64:" .env; then
echo " → Generating application key..."
php artisan key:generate --force
else
echo " → Application key already set"
fi
# Set APP_URL if provided
if [ -n "${APP_URL}" ]; then
sed -i "s@APP_URL=.*@APP_URL=${APP_URL}@" .env
fi
# ============================================
# Run Database Migrations
# ============================================
echo "[4/6] Running database migrations..."
php artisan migrate --force
# ============================================
# Laravel Optimizations
# ============================================
echo "[5/6] Optimizing Laravel..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
# ============================================
# Stop temporary MySQL instance
# ============================================
echo "[6/6] Stopping temporary MySQL instance..."
mysqladmin -u root -p"${MYSQL_ROOT_PASSWORD}" shutdown
wait $MYSQL_PID
echo "=========================================="
echo "Initialization complete!"
echo "=========================================="
echo ""
echo "Database Credentials (save these!):"
echo " Database: ${DB_DATABASE}"
echo " Username: ${DB_USERNAME}"
echo " Password: ${DB_PASSWORD}"
echo " Root Password: ${MYSQL_ROOT_PASSWORD}"
echo ""
echo "Starting all services with supervisord..."
echo "=========================================="
# Execute the command passed to the container (supervisord)
exec "$@"

View file

@ -0,0 +1,109 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# =========================================
# Frontend Static Assets (served directly by nginx) - MUST BE FIRST
# =========================================
# Use ^~ to prevent regex locations from matching
location ^~ /assets/ {
alias /var/www/frontend/build/client/assets/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# =========================================
# Backend API Routes (Laravel)
# =========================================
root /var/www/backend/public;
index index.php index.html;
location ~ ^/(api|sanctum)/ {
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;
# Laravel-specific settings
fastcgi_param HTTP_PROXY "";
fastcgi_read_timeout 300;
fastcgi_buffer_size 128k;
fastcgi_buffers 256 16k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
}
}
# Handle API routes that don't map to files
location ~ ^/(api|sanctum) {
try_files $uri /index.php?$query_string;
}
# PHP file handler for backend
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;
}
# =========================================
# Frontend Routes (React Router via Node.js)
# =========================================
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Standard proxy headers
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;
# WebSocket support (for React Router HMR if needed)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# =========================================
# Security & Performance
# =========================================
# Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Disable logging for favicon and robots.txt
location = /favicon.ico {
access_log off;
log_not_found off;
}
location = /robots.txt {
access_log off;
log_not_found off;
}
# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}

View file

@ -0,0 +1,52 @@
[supervisord]
nodaemon=true
user=root
logfile=/var/log/supervisor/supervisord.log
pidfile=/var/run/supervisord.pid
loglevel=info
[program:mysql]
command=/usr/sbin/mysqld --user=mysql --console
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/mysql.log
stderr_logfile=/var/log/supervisor/mysql_error.log
priority=1
[program:php-fpm]
command=/usr/sbin/php-fpm8.2 -F
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/php-fpm.log
stderr_logfile=/var/log/supervisor/php-fpm_error.log
priority=10
[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/nginx.log
stderr_logfile=/var/log/supervisor/nginx_error.log
priority=20
[program:frontend]
command=/usr/bin/node /var/www/frontend/node_modules/@react-router/serve/dist/cli.js /var/www/frontend/build/server/index.js
directory=/var/www/frontend
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/frontend.log
stderr_logfile=/var/log/supervisor/frontend_error.log
environment=PORT=3000,NODE_ENV=production
priority=30
user=www-data
[program:queue-worker]
command=/usr/bin/php /var/www/backend/artisan queue:work --sleep=3 --tries=3 --max-time=3600
directory=/var/www/backend
autostart=true
autorestart=true
stdout_logfile=/var/log/supervisor/queue-worker.log
stderr_logfile=/var/log/supervisor/queue-worker_error.log
priority=40
user=www-data
numprocs=1

View file

@ -49,6 +49,10 @@ fi
echo -e "${GREEN}=== Backend Setup ===${NC}" echo -e "${GREEN}=== Backend Setup ===${NC}"
cd "$PROJECT_ROOT/backend" cd "$PROJECT_ROOT/backend"
# Set compose file paths
COMPOSE_FILE="$PROJECT_ROOT/.docker/development/docker-compose.yml"
COMPOSE_OVERRIDE="$PROJECT_ROOT/.docker/development/docker-compose.override.yml"
# Install dependencies if vendor doesn't exist # Install dependencies if vendor doesn't exist
if [ ! -d "vendor" ]; then if [ ! -d "vendor" ]; then
echo -e "${YELLOW}No vendor directory found. Installing dependencies with Docker...${NC}" echo -e "${YELLOW}No vendor directory found. Installing dependencies with Docker...${NC}"
@ -88,7 +92,7 @@ fi
# Start containers using compose directly (docker-compose.override.yml is automatically used) # Start containers using compose directly (docker-compose.override.yml is automatically used)
echo -e "${YELLOW}Starting backend containers...${NC}" echo -e "${YELLOW}Starting backend containers...${NC}"
if $CONTAINER_CLI compose up -d 2>&1 | tee /tmp/compose-up.log; then if $CONTAINER_CLI compose -f "$COMPOSE_FILE" -f "$COMPOSE_OVERRIDE" up -d 2>&1 | tee /tmp/compose-up.log; then
echo -e "${GREEN}✓ Containers started${NC}\n" echo -e "${GREEN}✓ Containers started${NC}\n"
else else
echo -e "${RED}✗ Failed to start containers. Check /tmp/compose-up.log for details${NC}" echo -e "${RED}✗ Failed to start containers. Check /tmp/compose-up.log for details${NC}"
@ -100,7 +104,7 @@ echo -e "${YELLOW}Waiting for database to be ready...${NC}"
MAX_ATTEMPTS=30 MAX_ATTEMPTS=30
ATTEMPT=0 ATTEMPT=0
while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
if $CONTAINER_CLI compose exec mysql mysqladmin ping -h localhost --silent 2>/dev/null; then if $CONTAINER_CLI compose -f "$COMPOSE_FILE" -f "$COMPOSE_OVERRIDE" exec mysql mysqladmin ping -h localhost --silent 2>/dev/null; then
echo -e "${GREEN}✓ Database is ready${NC}" echo -e "${GREEN}✓ Database is ready${NC}"
break break
fi fi
@ -124,11 +128,11 @@ fi
# Run migrations # Run migrations
echo -e "${YELLOW}Running database migrations...${NC}" echo -e "${YELLOW}Running database migrations...${NC}"
$CONTAINER_CLI compose exec backend php artisan migrate --force $CONTAINER_CLI compose -f "$COMPOSE_FILE" -f "$COMPOSE_OVERRIDE" exec backend php artisan migrate --force
# Check if database has data # Check if database has data
echo -e "${YELLOW}Checking if database needs seeding...${NC}" echo -e "${YELLOW}Checking if database needs seeding...${NC}"
TABLE_COUNT=$($CONTAINER_CLI compose exec backend php artisan tinker --execute="echo \DB::table('users')->count();" 2>/dev/null | tail -1 | tr -d '[:space:]' || echo "0") TABLE_COUNT=$($CONTAINER_CLI compose -f "$COMPOSE_FILE" -f "$COMPOSE_OVERRIDE" exec backend php artisan tinker --execute="echo \DB::table('users')->count();" 2>/dev/null | tail -1 | tr -d '[:space:]' || echo "0")
# Default to 0 if not a number # Default to 0 if not a number
if ! [[ "$TABLE_COUNT" =~ ^[0-9]+$ ]]; then if ! [[ "$TABLE_COUNT" =~ ^[0-9]+$ ]]; then
TABLE_COUNT=0 TABLE_COUNT=0
@ -137,7 +141,7 @@ if [ "$TABLE_COUNT" -eq "0" ]; then
echo -e "${YELLOW}Database is empty. Run seeders? (y/n)${NC}" echo -e "${YELLOW}Database is empty. Run seeders? (y/n)${NC}"
read -r response read -r response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
$CONTAINER_CLI compose exec backend php artisan db:seed $CONTAINER_CLI compose -f "$COMPOSE_FILE" -f "$COMPOSE_OVERRIDE" exec backend php artisan db:seed
echo -e "${GREEN}✓ Database seeded${NC}\n" echo -e "${GREEN}✓ Database seeded${NC}\n"
fi fi
fi fi
@ -153,7 +157,7 @@ echo -e "${GREEN}Database:${NC} MySQL on localhost:3306"
echo -e "" echo -e ""
echo -e "${YELLOW}Note:${NC} Frontend container will install dependencies and start automatically." echo -e "${YELLOW}Note:${NC} Frontend container will install dependencies and start automatically."
echo -e "${YELLOW}To view frontend logs:${NC}" echo -e "${YELLOW}To view frontend logs:${NC}"
echo -e " cd backend && $CONTAINER_CLI compose logs -f frontend" echo -e " $CONTAINER_CLI compose -f .docker/development/docker-compose.yml -f .docker/development/docker-compose.override.yml logs -f frontend"
echo -e "" echo -e ""
echo -e "${YELLOW}To stop all services:${NC}" echo -e "${YELLOW}To stop all services:${NC}"
echo -e " cd backend && $CONTAINER_CLI compose down" echo -e " $CONTAINER_CLI compose -f .docker/development/docker-compose.yml -f .docker/development/docker-compose.override.yml down"