From a2f5a112d38690d7c570166307743e0bb907ea5b Mon Sep 17 00:00:00 2001 From: myrmidex Date: Fri, 26 Sep 2025 01:13:44 +0200 Subject: [PATCH] Set up docker for local and production --- .env.local | 41 ++++++++++ backend/bootstrap/cache/.gitignore | 0 backend/storage/app/.gitignore | 0 backend/storage/app/private/.gitignore | 0 backend/storage/app/public/.gitignore | 0 backend/storage/framework/.gitignore | 0 backend/storage/framework/cache/.gitignore | 0 .../storage/framework/cache/data/.gitignore | 0 backend/storage/framework/sessions/.gitignore | 0 backend/storage/framework/testing/.gitignore | 0 backend/storage/framework/views/.gitignore | 0 backend/storage/logs/.gitignore | 0 docker-compose.dev.yml | 79 +++++++++++++++++++ docker-compose.prod.yml | 58 ++++++++++++++ docker/backend/Dockerfile.dev | 43 ++++++++++ docker/backend/Dockerfile.prod | 69 ++++++++++++++++ docker/backend/supervisord.conf | 21 +++++ docker/frontend/Dockerfile.dev | 18 +++++ docker/frontend/Dockerfile.prod | 30 +++++++ docker/nginx/backend.conf | 40 ++++++++++ docker/nginx/frontend.conf | 27 +++++++ 21 files changed, 426 insertions(+) create mode 100644 .env.local mode change 100644 => 100755 backend/bootstrap/cache/.gitignore mode change 100644 => 100755 backend/storage/app/.gitignore mode change 100644 => 100755 backend/storage/app/private/.gitignore mode change 100644 => 100755 backend/storage/app/public/.gitignore mode change 100644 => 100755 backend/storage/framework/.gitignore mode change 100644 => 100755 backend/storage/framework/cache/.gitignore mode change 100644 => 100755 backend/storage/framework/cache/data/.gitignore mode change 100644 => 100755 backend/storage/framework/sessions/.gitignore mode change 100644 => 100755 backend/storage/framework/testing/.gitignore mode change 100644 => 100755 backend/storage/framework/views/.gitignore mode change 100644 => 100755 backend/storage/logs/.gitignore create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.prod.yml create mode 100644 docker/backend/Dockerfile.dev create mode 100644 docker/backend/Dockerfile.prod create mode 100644 docker/backend/supervisord.conf create mode 100644 docker/frontend/Dockerfile.dev create mode 100644 docker/frontend/Dockerfile.prod create mode 100644 docker/nginx/backend.conf create mode 100644 docker/nginx/frontend.conf diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..5aea6de --- /dev/null +++ b/.env.local @@ -0,0 +1,41 @@ +# Application +APP_NAME="Trip Planner" +APP_ENV=local +APP_KEY=base64:0YqH2qbjRZYGieiv8EiDVGUqEKLxT5Zwmv3nY9S75cc= +APP_DEBUG=true +APP_URL=http://localhost:8000 + +# Database +DB_CONNECTION=mysql +DB_HOST=database +DB_PORT=3306 +DB_DATABASE=trip_planner +DB_USERNAME=trip_user +DB_PASSWORD=secret + +# Redis +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +# Cache & Session +CACHE_DRIVER=redis +SESSION_DRIVER=redis +QUEUE_CONNECTION=redis + +# Mail (using Mailpit for local development) +MAIL_MAILER=smtp +MAIL_HOST=mailpit +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@tripplanner.local" +MAIL_FROM_NAME="${APP_NAME}" + +# Frontend URL for CORS +FRONTEND_URL=http://localhost:5173 + +# API Settings +API_PREFIX=api +SANCTUM_STATEFUL_DOMAINS=localhost:5173 \ No newline at end of file diff --git a/backend/bootstrap/cache/.gitignore b/backend/bootstrap/cache/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/app/.gitignore b/backend/storage/app/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/app/private/.gitignore b/backend/storage/app/private/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/app/public/.gitignore b/backend/storage/app/public/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/.gitignore b/backend/storage/framework/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/cache/.gitignore b/backend/storage/framework/cache/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/cache/data/.gitignore b/backend/storage/framework/cache/data/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/sessions/.gitignore b/backend/storage/framework/sessions/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/testing/.gitignore b/backend/storage/framework/testing/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/framework/views/.gitignore b/backend/storage/framework/views/.gitignore old mode 100644 new mode 100755 diff --git a/backend/storage/logs/.gitignore b/backend/storage/logs/.gitignore old mode 100644 new mode 100755 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..edda3f3 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,79 @@ +version: '3.8' + +services: + frontend: + build: + context: ./frontend + dockerfile: ../docker/frontend/Dockerfile.dev + container_name: trip-planner-frontend-dev + ports: + - "5173:5173" + volumes: + - ./frontend:/app:Z + - node_modules:/app/node_modules + environment: + - NODE_ENV=development + networks: + - trip-planner-network + + backend: + build: + context: ./backend + dockerfile: ../docker/backend/Dockerfile.dev + container_name: trip-planner-backend-dev + ports: + - "8000:8000" + volumes: + - ./backend:/var/www/html:Z + - vendor:/var/www/html/vendor + env_file: + - .env.local + depends_on: + - database + - redis + networks: + - trip-planner-network + + database: + image: docker.io/library/mariadb:11 + container_name: trip-planner-db-dev + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-secret} + MYSQL_DATABASE: ${DB_DATABASE:-trip_planner} + MYSQL_USER: ${DB_USERNAME:-trip_user} + MYSQL_PASSWORD: ${DB_PASSWORD:-secret} + volumes: + - db-data:/var/lib/mysql + networks: + - trip-planner-network + + redis: + image: docker.io/library/redis:alpine + container_name: trip-planner-redis-dev + ports: + - "6379:6379" + volumes: + - redis-data:/data + networks: + - trip-planner-network + + mailpit: + image: docker.io/axllent/mailpit:latest + container_name: trip-planner-mailpit-dev + ports: + - "1025:1025" + - "8025:8025" + networks: + - trip-planner-network + +networks: + trip-planner-network: + driver: bridge + +volumes: + db-data: + redis-data: + node_modules: + vendor: \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..1063834 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,58 @@ +version: '3.8' + +services: + frontend: + build: + context: ./frontend + dockerfile: ../docker/frontend/Dockerfile.prod + container_name: trip-planner-frontend + ports: + - "${FRONTEND_PORT:-80}: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: + APP_ENV: production + APP_DEBUG: false + APP_URL: ${APP_URL} + DB_CONNECTION: mysql + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT:-3306} + DB_DATABASE: ${DB_DATABASE} + DB_USERNAME: ${DB_USERNAME} + DB_PASSWORD: ${DB_PASSWORD} + REDIS_HOST: redis + REDIS_PORT: 6379 + CACHE_DRIVER: redis + QUEUE_CONNECTION: redis + SESSION_DRIVER: redis + depends_on: + - redis + restart: unless-stopped + networks: + - trip-planner-network + + redis: + image: docker.io/library/redis:alpine + container_name: trip-planner-redis + volumes: + - redis-data:/data + command: redis-server --appendonly yes + restart: unless-stopped + networks: + - trip-planner-network + +networks: + trip-planner-network: + driver: bridge + +volumes: + redis-data: \ No newline at end of file diff --git a/docker/backend/Dockerfile.dev b/docker/backend/Dockerfile.dev new file mode 100644 index 0000000..ce59d40 --- /dev/null +++ b/docker/backend/Dockerfile.dev @@ -0,0 +1,43 @@ +FROM php:8.3-fpm-alpine + +# Install system dependencies +RUN apk add --no-cache \ + git \ + curl \ + libpng-dev \ + oniguruma-dev \ + libxml2-dev \ + zip \ + unzip \ + nodejs \ + npm \ + shadow + +# Install PHP extensions +RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd + +# Install Composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +WORKDIR /var/www/html + +# Create developer user with UID 1000 (same as host user) +RUN adduser -u 1000 -s /bin/sh -D developer + +# Create storage and bootstrap/cache directories +RUN mkdir -p storage/app/public storage/framework/cache storage/framework/sessions storage/framework/views storage/logs bootstrap/cache + +# Change ownership to developer user +RUN chown -R developer:developer /var/www/html + +# Set proper permissions for Laravel directories +RUN chmod -R 775 storage bootstrap/cache + +# Switch to developer user +USER developer + +# Expose port 8000 for artisan serve +EXPOSE 8000 + +# Start Laravel development server with composer install +CMD sh -c "composer install && php artisan key:generate --force && php artisan serve --host=0.0.0.0 --port=8000" \ No newline at end of file diff --git a/docker/backend/Dockerfile.prod b/docker/backend/Dockerfile.prod new file mode 100644 index 0000000..252525d --- /dev/null +++ b/docker/backend/Dockerfile.prod @@ -0,0 +1,69 @@ +# Build stage +FROM php:8.3-fpm-alpine AS builder + +# Install system 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 composer.json composer.lock ./ + +# Install dependencies (no dev) +RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist + +# Copy application +COPY . . + +# Generate optimized autoloader +RUN composer dump-autoload --optimize --classmap-authoritative + +# Production stage +FROM php:8.3-fpm-alpine + +# Install runtime dependencies +RUN apk add --no-cache \ + libpng-dev \ + oniguruma-dev \ + libxml2-dev \ + nginx \ + supervisor + +# Install PHP extensions +RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd opcache + +WORKDIR /var/www/html + +# Copy application from builder +COPY --from=builder /var/www/html . + +# Copy nginx config +COPY docker/nginx/backend.conf /etc/nginx/http.d/default.conf + +# Copy supervisord config +COPY docker/backend/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# Configure PHP-FPM +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 + +# Set permissions +RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache + +# Expose port 80 +EXPOSE 80 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] \ No newline at end of file diff --git a/docker/backend/supervisord.conf b/docker/backend/supervisord.conf new file mode 100644 index 0000000..cd81468 --- /dev/null +++ b/docker/backend/supervisord.conf @@ -0,0 +1,21 @@ +[supervisord] +nodaemon=true +user=root + +[program:php-fpm] +command=/usr/local/sbin/php-fpm -F +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 \ No newline at end of file diff --git a/docker/frontend/Dockerfile.dev b/docker/frontend/Dockerfile.dev new file mode 100644 index 0000000..4f242bc --- /dev/null +++ b/docker/frontend/Dockerfile.dev @@ -0,0 +1,18 @@ +FROM node:20-alpine + +# Install global dependencies as root +RUN npm install -g vite + +WORKDIR /app + +# Change ownership of /app to node user (UID 1000) +RUN chown -R node:node /app + +# Switch to node user (UID 1000, same as host user) +USER node + +# Expose Vite dev server port +EXPOSE 5173 + +# Start development server +CMD ["sh", "-c", "npm install && npm run dev -- --host 0.0.0.0"] \ No newline at end of file diff --git a/docker/frontend/Dockerfile.prod b/docker/frontend/Dockerfile.prod new file mode 100644 index 0000000..7b2a8ce --- /dev/null +++ b/docker/frontend/Dockerfile.prod @@ -0,0 +1,30 @@ +# Build stage +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci --only=production + +# Copy app files +COPY . . + +# Build the app +RUN npm run build + +# Production stage +FROM nginx:alpine + +# Copy built app from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy nginx config +COPY docker/nginx/frontend.conf /etc/nginx/conf.d/default.conf + +# Expose port 80 +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker/nginx/backend.conf b/docker/nginx/backend.conf new file mode 100644 index 0000000..f07eaa1 --- /dev/null +++ b/docker/nginx/backend.conf @@ -0,0 +1,40 @@ +server { + listen 80; + server_name localhost; + root /var/www/html/public; + index index.php; + + # Disable access log for performance + access_log off; + error_log /var/log/nginx/error.log error; + + # 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 text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype; + + location / { + 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; + } + + location ~ /\.(?!well-known).* { + deny all; + } + + # 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; +} \ No newline at end of file diff --git a/docker/nginx/frontend.conf b/docker/nginx/frontend.conf new file mode 100644 index 0000000..af85107 --- /dev/null +++ b/docker/nginx/frontend.conf @@ -0,0 +1,27 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # 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 text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype; + + location / { + 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"; + } + + # 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; +} \ No newline at end of file