diff --git a/Dockerfile b/Dockerfile index ded4b42..cbcd772 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,25 +36,24 @@ ENV APP_ENV=production \ MAIL_MAILER=smtp \ MAIL_ENCRYPTION=tls -# Copy composer files and install PHP dependencies -COPY composer.json composer.lock ./ +# Copy application code first +COPY . . + +# Install PHP dependencies (production only) RUN composer install --no-dev --no-interaction --optimize-autoloader -# Copy package files and install Node dependencies -COPY package*.json ./ -RUN npm ci --omit=dev - -# Copy application code -COPY . . +# Install ALL Node dependencies (including dev for building) +RUN npm ci # Build frontend assets RUN npm run build +# Remove node_modules after build to save space +RUN rm -rf node_modules + # Laravel optimizations RUN php artisan config:cache \ && php artisan route:cache \ - && php artisan view:cache \ - && php artisan livewire:discover \ && composer dump-autoload --optimize # Set permissions @@ -93,5 +92,31 @@ EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/up || exit 1 +# Create startup script for production +RUN cat > /start-prod.sh <<'EOF' +#!/bin/sh +set -e + +# Wait for database to be ready +echo "Waiting for database..." +for i in $(seq 1 30); do + if php artisan db:monitor --database=mysql 2>/dev/null | grep -q "OK"; then + echo "Database is ready!" + break + fi + echo "Waiting for database... ($i/30)" + sleep 2 +done + +# Run migrations +echo "Running migrations..." +php artisan migrate --force || echo "Migrations failed or already up-to-date" + # Start FrankenPHP -CMD ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"] +exec frankenphp run --config /etc/caddy/Caddyfile +EOF + +RUN chmod +x /start-prod.sh + +# Start with our script +CMD ["/start-prod.sh"] diff --git a/Makefile b/Makefile index 272fb3d..01617f9 100644 --- a/Makefile +++ b/Makefile @@ -38,21 +38,17 @@ logs-db: ## Show database logs docker compose logs -f db # Production Commands -.PHONY: prod -prod: ## Start production environment - docker compose -f docker-compose.prod.yml up -d - .PHONY: prod-build -prod-build: ## Build production image - docker compose -f docker-compose.prod.yml build +prod-build: ## Build production image for Codeberg + ./bin/build-push.sh -.PHONY: prod-stop -prod-stop: ## Stop production environment - docker compose -f docker-compose.prod.yml down +.PHONY: prod-build-tag +prod-build-tag: ## Build with specific tag (usage: make prod-build-tag TAG=v1.0.0) + ./bin/build-push.sh $(TAG) -.PHONY: prod-logs -prod-logs: ## Show production logs - docker compose -f docker-compose.prod.yml logs -f +.PHONY: prod-login +prod-login: ## Login to Codeberg registry + podman login codeberg.org # Laravel Commands .PHONY: artisan diff --git a/bin/build-push.sh b/bin/build-push.sh new file mode 100755 index 0000000..532b43f --- /dev/null +++ b/bin/build-push.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Build and push production image to Codeberg + +set -e + +# Configuration +REGISTRY="codeberg.org" +NAMESPACE="lvl0" +IMAGE_NAME="dish-planner" +TAG="${1:-latest}" + +echo "🔨 Building production image..." +podman build -f Dockerfile -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG} . + +echo "📤 Pushing to Codeberg registry..." +echo "Please ensure you're logged in to Codeberg:" +echo " podman login codeberg.org" + +podman push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG} + +echo "✅ Done! Image pushed to ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${TAG}" +echo "" +echo "To deploy in production:" +echo "1. Copy docker-compose.prod.yml to your server" +echo "2. Set required environment variables:" +echo " - APP_KEY (generate with: openssl rand -base64 32)" +echo " - APP_URL" +echo " - DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_ROOT_PASSWORD" +echo "3. Run: docker-compose -f docker-compose.prod.yml up -d" \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 9d61a89..f39bab7 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,12 +1,7 @@ # Production Docker Compose -version: '3.8' - services: app: - build: - context: . - dockerfile: Dockerfile - image: dishplanner:latest + image: codeberg.org/lvl0/dish-planner:latest container_name: dishplanner_app restart: always ports: @@ -18,8 +13,8 @@ services: DB_DATABASE: "${DB_DATABASE}" DB_USERNAME: "${DB_USERNAME}" DB_PASSWORD: "${DB_PASSWORD}" - - # Optional email configuration + + # Optional email configuration MAIL_HOST: "${MAIL_HOST:-}" MAIL_PORT: "${MAIL_PORT:-587}" MAIL_USERNAME: "${MAIL_USERNAME:-}" @@ -30,8 +25,7 @@ services: - app_storage:/app/storage - app_logs:/app/storage/logs depends_on: - db: - condition: service_healthy + - db networks: - dishplanner healthcheck: @@ -50,22 +44,10 @@ services: MYSQL_USER: "${DB_USERNAME}" MYSQL_PASSWORD: "${DB_PASSWORD}" MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}" - # MariaDB performance tuning - MYSQL_INITDB_SKIP_TZINFO: 1 - MYSQL_CHARACTER_SET_SERVER: utf8mb4 - MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci volumes: - db_data:/var/lib/mysql - - ./database/backups:/backups # For backup scripts networks: - dishplanner - healthcheck: - test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 60s - # No ports exposed - only accessible within network # Optional: Redis for production caching/sessions # redis: @@ -105,9 +87,6 @@ services: networks: dishplanner: driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 volumes: db_data: @@ -117,4 +96,4 @@ volumes: app_logs: driver: local # redis_data: - # driver: local \ No newline at end of file + # driver: local diff --git a/shell.nix b/shell.nix index fb1f0f9..f70fd0b 100644 --- a/shell.nix +++ b/shell.nix @@ -52,6 +52,45 @@ pkgs.mkShell { podman-compose exec app php artisan "$@" } + prod-build() { + local TAG="''${1:-latest}" + local REGISTRY="codeberg.org" + local NAMESPACE="lvl0" + local IMAGE_NAME="dish-planner" + + echo "🔨 Building production image..." + podman build -f Dockerfile -t ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG} . + + echo "✅ Build complete: ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}" + echo "Run 'prod-push' to push to Codeberg" + } + + prod-push() { + local TAG="''${1:-latest}" + local REGISTRY="codeberg.org" + local NAMESPACE="lvl0" + local IMAGE_NAME="dish-planner" + + echo "📤 Pushing to Codeberg registry..." + if podman push ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}; then + echo "✅ Image pushed to ''${REGISTRY}/''${NAMESPACE}/''${IMAGE_NAME}:''${TAG}" + else + echo "❌ Failed to push image. Did you run 'prod-build' first?" + echo " Also make sure you're logged in with 'prod-login'" + return 1 + fi + } + + prod-build-push() { + local TAG="''${1:-latest}" + prod-build "$TAG" && prod-push "$TAG" + } + + prod-login() { + echo "📝 Logging into Codeberg registry..." + podman login codeberg.org + } + echo "🚀 Dish Planner Development Environment" echo "=======================================" echo "PHP: $(php --version | head -n1)" @@ -59,17 +98,18 @@ pkgs.mkShell { echo "Podman: $(podman --version)" echo "Podman-compose: $(podman-compose --version 2>/dev/null || echo 'checking...')" echo "" - echo "Quick commands:" + echo "Development commands:" echo " rebuild - Full rebuild (removes volumes)" echo " rebuild-quick - Quick rebuild (keeps volumes)" echo " logs [service] - Follow logs (default: all)" echo " shell - Enter app container" echo " artisan [cmd] - Run artisan commands" echo "" - echo "Standard commands:" - echo " podman-compose up -d - Start containers" - echo " podman-compose down - Stop containers" - echo " make dev - Start via Makefile" + echo "Production commands:" + echo " prod-login - Login to Codeberg registry" + echo " prod-build [tag] - Build production image (default: latest)" + echo " prod-push [tag] - Push image to Codeberg" + echo " prod-build-push - Build and push in one command" echo "" # Auto-start prompt