2 - tmp
This commit is contained in:
parent
a7036cda3d
commit
a5307f3e5d
13 changed files with 1818 additions and 40 deletions
63
.dockerignore
Normal file
63
.dockerignore
Normal 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
6
.env
Normal 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
100
.woodpecker.yml
Normal 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
134
Dockerfile.prod
Normal 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
48
backend/.dockerignore
Normal 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
|
||||
|
|
@ -1,58 +1,66 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
frontend:
|
||||
app:
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: ../docker/frontend/Dockerfile.prod
|
||||
container_name: trip-planner-frontend
|
||||
context: .
|
||||
dockerfile: Dockerfile.prod
|
||||
container_name: trip-planner-production
|
||||
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"
|
||||
- "${APP_PORT:-8080}:80"
|
||||
environment:
|
||||
# Laravel Application
|
||||
APP_NAME: "Trip Planner"
|
||||
APP_ENV: production
|
||||
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_HOST: ${DB_HOST}
|
||||
DB_PORT: ${DB_PORT:-3306}
|
||||
DB_DATABASE: ${DB_DATABASE}
|
||||
DB_USERNAME: ${DB_USERNAME}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
REDIS_HOST: redis
|
||||
DB_HOST: 127.0.0.1
|
||||
DB_PORT: 3306
|
||||
DB_DATABASE: ${DB_DATABASE:-trip_planner}
|
||||
DB_USERNAME: ${DB_USERNAME:-trip_user}
|
||||
DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD must be set}
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD must be set}
|
||||
|
||||
# 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
|
||||
depends_on:
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- trip-planner-network
|
||||
SESSION_LIFETIME: 120
|
||||
|
||||
redis:
|
||||
image: docker.io/library/redis:alpine
|
||||
container_name: trip-planner-redis
|
||||
# Mail (configure as needed)
|
||||
MAIL_MAILER: ${MAIL_MAILER:-log}
|
||||
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:
|
||||
- redis-data:/data
|
||||
command: redis-server --appendonly yes
|
||||
# Persistent data for database
|
||||
- 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
|
||||
networks:
|
||||
- trip-planner-network
|
||||
|
||||
networks:
|
||||
trip-planner-network:
|
||||
driver: bridge
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/up"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
db-data:
|
||||
redis-data:
|
||||
storage-data:
|
||||
160
docker/README.md
Normal file
160
docker/README.md
Normal 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
342
docker/dev/README.md
Normal 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
141
docker/entrypoint.sh
Normal 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 "$@"
|
||||
100
docker/nginx/production.conf
Normal file
100
docker/nginx/production.conf
Normal 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
559
docker/prod/README.md
Normal 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
66
docker/supervisord.conf
Normal 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
51
frontend/.dockerignore
Normal 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*
|
||||
Loading…
Reference in a new issue