4 Commits

Author SHA1 Message Date
Frank John Begornia
02c7f4e2aa Refactor Docker setup for Laravel 5.0 compatibility and optimize deployment process
- Updated Dockerfile configurations for various environments (alpine, basic, cloudrun, minimal, simple, test) to ensure compatibility with Laravel 5.0 and PHP 5.6.
- Added PHP compatibility documentation outlining mcrypt requirements and upgrade paths.
- Created cloudbuild.yaml for Google Cloud Build integration to streamline deployment to Cloud Run.
- Introduced docker-compose.local.yml for local development with MySQL and Redis services.
- Enhanced rebuild.sh script for easier local development and migration handling.
- Updated Laravel Blade views to correct asset paths.
- Added start-local.sh script for quick local setup and service management.
2025-08-11 23:14:31 +08:00
franknstayn
70496dc874 updated 2024-05-23 01:42:59 +08:00
franknstayn
97bc491b07 added tax exception grayslake 2024-05-23 01:41:03 +08:00
Frank John Begornia
a2aba7b8e9 added tax 2024-02-16 22:43:25 +08:00
18 changed files with 1286 additions and 39 deletions

63
.dockerignore Normal file
View File

@@ -0,0 +1,63 @@
# Git files
.git
.gitignore
.gitattributes
# Docker files
Dockerfile*
docker-compose*.yml
.dockerignore
# Documentation
README.md
DEPLOYMENT_GUIDE.md
*.md
# Development files
.env.example
.env.local
.env.testing
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Node modules and build files
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Laravel specific
/vendor
/storage/logs/*
!/storage/logs/.gitkeep
/storage/framework/cache/*
!/storage/framework/cache/.gitkeep
/storage/framework/sessions/*
!/storage/framework/sessions/.gitkeep
/storage/framework/views/*
!/storage/framework/views/.gitkeep
/bootstrap/cache/*
!/bootstrap/cache/.gitkeep
# Testing
/tests
phpunit.xml
phpspec.yml
# Build tools
gulpfile.js
package.json
package-lock.json
yarn.lock
# Scripts
start-local.sh
rebuild.sh

229
DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,229 @@
# Google Cloud Run Deployment Guide for Merchbay Laravel Application
This guide will help you deploy your Laravel 5 application to Google Cloud Run using the provided `cloudbuild.yaml` configuration.
## Prerequisites
1. **Google Cloud Project**: Ensure you have a Google Cloud Project with billing enabled
2. **APIs Enabled**: Enable the following APIs:
- Cloud Build API
- Cloud Run API
- Container Registry API (or Artifact Registry API)
- Cloud SQL API (if using Cloud SQL)
- Secret Manager API (recommended for secrets)
3. **gcloud CLI**: Install and configure the Google Cloud CLI
## Pre-deployment Setup
### 1. Create Cloud SQL Instance (Recommended)
```bash
# Create MySQL instance
gcloud sql instances create merchbay-db \
--database-version=MYSQL_8_0 \
--tier=db-f1-micro \
--region=us-central1
# Create database
gcloud sql databases create merchbay --instance=merchbay-db
# Create user
gcloud sql users create laravel_user \
--instance=merchbay-db \
--password=YOUR_SECURE_PASSWORD
```
### 2. Store Secrets in Secret Manager
```bash
# Application key (generate a new one)
echo "base64:$(openssl rand -base64 32)" | gcloud secrets create APP_KEY --data-file=-
# Database password
echo "YOUR_DB_PASSWORD" | gcloud secrets create DB_PASSWORD --data-file=-
# PayPal secrets
echo "YOUR_PAYPAL_LIVE_SECRET" | gcloud secrets create PAYPAL_LIVE_SECRET --data-file=-
echo "YOUR_PAYPAL_SANDBOX_SECRET" | gcloud secrets create PAYPAL_SANDBOX_SECRET --data-file=-
# Mail password
echo "YOUR_MAIL_PASSWORD" | gcloud secrets create MAIL_PASSWORD --data-file=-
```
### 3. Update cloudbuild.yaml Variables
Update the substitution variables in `cloudbuild.yaml`:
```yaml
substitutions:
_SERVICE_NAME: 'merchbay-laravel'
_REGION: 'us-central1' # Change to your preferred region
_CLOUDSQL_INSTANCE: 'YOUR_PROJECT_ID:us-central1:merchbay-db'
_DB_DATABASE: 'merchbay'
_DB_USERNAME: 'laravel_user'
# ... other variables
```
## Deployment Steps
### 1. Set up Cloud Build Trigger
```bash
# Connect your repository to Cloud Build
gcloud builds triggers create github \
--repo-name=YOUR_REPO_NAME \
--repo-owner=YOUR_GITHUB_USERNAME \
--branch-pattern="^main$" \
--build-config=cloudbuild.yaml
# Or trigger manually
gcloud builds submit --config=cloudbuild.yaml .
```
### 2. Manual Deployment (Alternative)
```bash
# Set your project ID
export PROJECT_ID=your-project-id
# Build and deploy
gcloud builds submit --config=cloudbuild.yaml \
--substitutions=_PROJECT_ID=$PROJECT_ID,_SERVICE_NAME=merchbay-laravel
```
## Post-deployment Tasks
### 1. Run Database Migrations
```bash
# Create a one-time job for migrations
gcloud run jobs create laravel-migrate \
--image=gcr.io/$PROJECT_ID/merchbay-laravel:latest \
--region=us-central1 \
--set-env-vars="APP_ENV=production" \
--set-cloudsql-instances=$PROJECT_ID:us-central1:merchbay-db \
--command="php" \
--args="artisan,migrate,--force" \
--max-retries=1
# Execute the migration
gcloud run jobs execute laravel-migrate --region=us-central1 --wait
```
### 2. Set up Custom Domain (Optional)
```bash
# Map custom domain
gcloud run domain-mappings create \
--service=merchbay-laravel \
--domain=your-domain.com \
--region=us-central1
```
### 3. Configure Load Balancer and CDN (Optional)
For better performance, consider setting up:
- Cloud Load Balancer
- Cloud CDN for static assets
- Cloud Storage for file uploads
## Environment Variables Reference
The following environment variables are configured in the Cloud Run service:
### Application Settings
- `APP_ENV=production`
- `APP_DEBUG=false`
- `APP_KEY` (from Secret Manager)
### Database Settings
- `DB_HOST=127.0.0.1` (Cloud SQL Proxy)
- `DB_DATABASE=merchbay`
- `DB_USERNAME=laravel_user`
- `DB_PASSWORD` (from Secret Manager)
### Cache & Session
- `CACHE_DRIVER=redis` (requires Redis setup)
- `SESSION_DRIVER=redis`
- `QUEUE_DRIVER=database`
### PayPal Configuration
- `PAYPAL_MODE=live` (or 'sandbox' for testing)
- `PAYPAL_LIVE_CLIENT_ID`
- `PAYPAL_LIVE_SECRET`
- `PAYPAL_SANDBOX_CLIENT_ID`
- `PAYPAL_SANDBOX_SECRET`
### Mail Configuration
- `MAIL_DRIVER=smtp`
- `MAIL_HOST`
- `MAIL_PORT`
- `MAIL_USERNAME`
- `MAIL_PASSWORD`
## Monitoring and Logging
### Enable Application Insights
```bash
# Enable Cloud Logging
gcloud logging sinks create laravel-logs \
bigquery.googleapis.com/projects/$PROJECT_ID/datasets/app_logs
```
### Set up Uptime Monitoring
```bash
# Create uptime check
gcloud alpha monitoring uptime create \
--display-name="Merchbay Laravel App" \
--http-check-path="/" \
--hostname=your-service-url.run.app
```
## Troubleshooting
### Common Issues
1. **502 Bad Gateway**: Check logs with `gcloud run services logs read merchbay-laravel`
2. **Database Connection Issues**: Verify Cloud SQL instance and VPC configuration
3. **Memory Issues**: Increase memory allocation in cloudbuild.yaml
4. **Timeout Issues**: Increase timeout and check for long-running operations
### Debugging Commands
```bash
# View service logs
gcloud run services logs read merchbay-laravel --region=us-central1
# Get service details
gcloud run services describe merchbay-laravel --region=us-central1
# Check revisions
gcloud run revisions list --service=merchbay-laravel --region=us-central1
```
## Security Considerations
1. **Use Secret Manager** for all sensitive data
2. **Enable VPC** for database connections
3. **Configure IAM** with least privilege principle
4. **Enable Cloud Armor** for DDoS protection
5. **Use HTTPS** with managed SSL certificates
6. **Regular Updates** for dependencies and base images
## Cost Optimization
1. **Set Min Instances to 0** for cost savings
2. **Use Appropriate CPU/Memory** settings
3. **Implement Caching** (Redis/Memcached)
4. **Optimize Images** and use multi-stage builds
5. **Monitor Usage** with Cloud Monitoring
## Support
For issues specific to:
- **Cloud Run**: Check [Cloud Run documentation](https://cloud.google.com/run/docs)
- **Laravel**: Check [Laravel documentation](https://laravel.com/docs)
- **PayPal Integration**: Check [PayPal developer documentation](https://developer.paypal.com/)

View File

@@ -11,29 +11,23 @@ RUN apk --update --no-cache add \
zip \ zip \
unzip \ unzip \
libmcrypt-dev \ libmcrypt-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \ && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd pdo pdo_mysql zip mcrypt && docker-php-ext-install gd pdo pdo_mysql zip mcrypt
# Set the working directory in the container # Set the working directory in the container
WORKDIR /var/www WORKDIR /var/www
# Clear cache
# RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Copy the Laravel application files to the container # Copy the Laravel application files to the container
COPY . . COPY . .
# Set appropriate permissions for Laravel storage and bootstrap cache # Set appropriate permissions for Laravel storage and bootstrap cache
RUN chown -R www-data:www-data storage bootstrap RUN chown -R www-data:www-data storage bootstrap/cache
# Install Composer # Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Install Laravel dependencies # Install Laravel dependencies
RUN composer install --no-plugins --no-scripts RUN composer install --no-interaction --no-plugins --no-scripts
# Generate Laravel application key
RUN php artisan key:generate
# Create directory for the socket and set permissions # Create directory for the socket and set permissions
RUN mkdir -p /run/php && chown www-data:www-data /run/php RUN mkdir -p /run/php && chown www-data:www-data /run/php

71
Dockerfile.alpine Normal file
View File

@@ -0,0 +1,71 @@
# Debug Dockerfile - Let's try the original approach that was working
FROM php:5.6-fpm-alpine
# Set working directory
WORKDIR /var/www
# Install system dependencies and PHP extensions
RUN apk --update --no-cache add \
nginx \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
zip \
unzip \
libmcrypt-dev \
curl \
git \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd pdo pdo_mysql zip mcrypt mbstring
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Copy the Laravel application files to the container
COPY . .
# Set appropriate permissions for Laravel storage and bootstrap cache
RUN chown -R www-data:www-data storage bootstrap/cache
# Install Laravel dependencies
RUN composer install --no-interaction --no-plugins --no-scripts --no-dev
# Create .env file
RUN if [ ! -f .env ]; then cp .env.example .env; fi
# Generate application key
RUN php artisan key:generate || true
# Create directory for the socket and set permissions
RUN mkdir -p /run/php && chown www-data:www-data /run/php
# Configure nginx for Laravel
RUN echo 'server {
listen 80;
server_name localhost;
root /var/www/public;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}' > /etc/nginx/conf.d/default.conf
# Create startup script
RUN echo '#!/bin/sh
php-fpm -D
nginx -g "daemon off;"' > /start.sh && chmod +x /start.sh
# Expose port 80
EXPOSE 80
# Start both PHP-FPM and Nginx
CMD ["/start.sh"]

39
Dockerfile.basic Normal file
View File

@@ -0,0 +1,39 @@
# Bare minimum working Dockerfile for Laravel 5.0
FROM php:5.6-apache
WORKDIR /var/www/html
# Install absolute essentials only
RUN apt-get update \
&& apt-get install -y \
curl \
git \
libmcrypt-dev \
&& docker-php-ext-install \
pdo \
pdo_mysql \
mcrypt \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Enable Apache rewrite
RUN a2enmod rewrite
# Configure Apache for Laravel
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
# Copy app and set permissions
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html
# Basic Laravel setup
RUN if [ ! -f .env ]; then cp .env.example .env; fi
RUN composer install --no-dev --no-interaction --ignore-platform-reqs
RUN php artisan key:generate || true
EXPOSE 80
CMD ["apache2-foreground"]

132
Dockerfile.cloudrun Normal file
View File

@@ -0,0 +1,132 @@
# Dockerfile optimized for Google Cloud Run
# Use PHP 5.6 with Apache (matches Laravel 5.0 requirements perfectly)
FROM php:5.6-apache
# Set working directory
WORKDIR /var/www/html
# Install system dependencies and PHP extensions required for Laravel
RUN apt-get update && apt-get install -y \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
libmcrypt-dev \
zlib1g-dev \
zip \
unzip \
git \
curl \
libxml2-dev \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install \
pdo \
pdo_mysql \
mbstring \
zip \
gd \
xml \
mcrypt \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Enable Apache modules and configure
RUN a2enmod rewrite headers
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
# Configure Apache document root for Laravel
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Copy Apache virtual host configuration
COPY <<EOF /etc/apache2/sites-available/000-default.conf
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/public
<Directory /var/www/html/public>
AllowOverride All
Require all granted
</Directory>
# Logging
ErrorLog \${APACHE_LOG_DIR}/error.log
CustomLog \${APACHE_LOG_DIR}/access.log combined
# Security headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</VirtualHost>
EOF
# Copy application files
COPY . /var/www/html/
# Fix Git ownership issue for the repository
RUN git config --global --add safe.directory /var/www/html || true
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Set proper permissions for Laravel
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html \
&& chmod -R 775 /var/www/html/storage \
&& chmod -R 775 /var/www/html/bootstrap/cache
# Create .env file from .env.example if it doesn't exist
RUN if [ ! -f /var/www/html/.env ]; then \
cp /var/www/html/.env.example /var/www/html/.env; \
fi
# Switch to www-data user for Laravel commands to avoid ownership issues
USER www-data
# Generate application key (will be overridden by environment variables in Cloud Run)
RUN php artisan key:generate || true
# Clear and cache configuration for production
RUN php artisan config:clear || true \
&& php artisan route:clear || true \
&& php artisan view:clear || true
# Switch back to root for the remaining setup
USER root
# Create startup script
COPY <<EOF /usr/local/bin/start.sh
#!/bin/bash
set -e
# Fix Git ownership issue
git config --global --add safe.directory /var/www/html || true
# Wait for database to be ready (if using Cloud SQL)
echo "Starting Laravel application..."
# Run Laravel optimizations
php artisan config:cache || true
php artisan route:cache || true
php artisan view:cache || true
# Start Apache in foreground
exec apache2-foreground
EOF
RUN chmod +x /usr/local/bin/start.sh
# Expose port 80 (Cloud Run will map this to 8080)
EXPOSE 80
# Health check for Cloud Run
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# Use the startup script
CMD ["/usr/local/bin/start.sh"]

86
Dockerfile.minimal Normal file
View File

@@ -0,0 +1,86 @@
# Minimal Dockerfile for Laravel 5.0 with PHP 5.6
FROM php:5.6-apache
# Set working directory
WORKDIR /var/www/html
# Install only essential system dependencies
RUN apt-get update && apt-get install -y \
libmcrypt-dev \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
zlib1g-dev \
zip \
unzip \
git \
curl \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install \
pdo \
pdo_mysql \
mcrypt \
gd \
zip \
mbstring \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Enable Apache modules
RUN a2enmod rewrite
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
# Configure Apache document root for Laravel
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf
# Simple Apache configuration
RUN echo '<VirtualHost *:80>\n\
DocumentRoot /var/www/html/public\n\
<Directory /var/www/html/public>\n\
AllowOverride All\n\
Require all granted\n\
</Directory>\n\
</VirtualHost>' > /etc/apache2/sites-available/000-default.conf
# Copy application files
COPY . /var/www/html/
# Fix Git ownership issue
RUN git config --global --add safe.directory /var/www/html || true
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Set proper permissions
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html \
&& chmod -R 775 /var/www/html/storage \
&& chmod -R 775 /var/www/html/bootstrap/cache
# Create .env file if it doesn't exist
RUN if [ ! -f /var/www/html/.env ]; then \
cp /var/www/html/.env.example /var/www/html/.env; \
fi
# Switch to www-data for Laravel commands
USER www-data
RUN php artisan key:generate || true
USER root
# Create simple startup script
RUN echo '#!/bin/bash\n\
set -e\n\
echo "Starting Laravel application..."\n\
exec apache2-foreground' > /usr/local/bin/start.sh \
&& chmod +x /usr/local/bin/start.sh
# Expose port 80
EXPOSE 80
# Start Apache
CMD ["/usr/local/bin/start.sh"]

75
Dockerfile.simple Normal file
View File

@@ -0,0 +1,75 @@
# Ultra-minimal Dockerfile for Laravel 5.0 with PHP 5.6
FROM php:5.6-apache
# Set working directory
WORKDIR /var/www/html
# Install packages one by one to identify issues
RUN apt-get update
# Install core development tools first
RUN apt-get install -y \
curl \
git \
zip \
unzip
# Install mcrypt (most important for Laravel 5.0)
RUN apt-get install -y libmcrypt-dev \
&& docker-php-ext-install mcrypt
# Install basic PHP extensions
RUN docker-php-ext-install \
pdo \
pdo_mysql \
mbstring
# Try to install GD dependencies
RUN apt-get install -y \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
&& docker-php-ext-install gd
# Install zip extension
RUN apt-get install -y zlib1g-dev \
&& docker-php-ext-install zip
# Clean up
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Enable Apache rewrite module
RUN a2enmod rewrite
# Configure Apache
RUN echo "ServerName localhost" >> /etc/apache2/apache2.conf
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
# Copy application files
COPY . /var/www/html/
# Set basic permissions
RUN chown -R www-data:www-data /var/www/html \
&& chmod -R 755 /var/www/html
# Create .env file if needed
RUN if [ ! -f /var/www/html/.env ]; then \
cp /var/www/html/.env.example /var/www/html/.env; \
fi
# Install dependencies without dev packages
RUN composer install --no-dev --no-interaction || true
# Generate Laravel key
RUN php artisan key:generate || true
# Expose port 80
EXPOSE 80
# Start Apache
CMD ["apache2-foreground"]

60
Dockerfile.test Normal file
View File

@@ -0,0 +1,60 @@
# Test Dockerfile to identify available packages
FROM php:5.6-apache
WORKDIR /var/www/html
# Update package lists first
RUN apt-get update
# Test each package individually
RUN apt-get install -y curl || echo "curl failed"
RUN apt-get install -y git || echo "git failed"
RUN apt-get install -y zip || echo "zip failed"
RUN apt-get install -y unzip || echo "unzip failed"
# Test mcrypt (most important)
RUN apt-get install -y libmcrypt-dev || echo "libmcrypt-dev failed"
# Test image libraries one by one
RUN apt-get install -y libpng-dev || echo "libpng-dev failed"
RUN apt-get install -y libjpeg-dev || echo "libjpeg-dev failed"
RUN apt-get install -y libfreetype6-dev || echo "libfreetype6-dev failed"
# Test zip library
RUN apt-get install -y zlib1g-dev || echo "zlib1g-dev failed"
# Try to install PHP extensions
RUN docker-php-ext-install mcrypt || echo "mcrypt extension failed"
RUN docker-php-ext-install pdo || echo "pdo extension failed"
RUN docker-php-ext-install pdo_mysql || echo "pdo_mysql extension failed"
RUN docker-php-ext-install mbstring || echo "mbstring extension failed"
# Test GD configuration
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ || echo "gd configure failed"
RUN docker-php-ext-install gd || echo "gd extension failed"
# Test zip extension
RUN docker-php-ext-install zip || echo "zip extension failed"
# Clean up
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Basic Apache setup
RUN a2enmod rewrite
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
# Test copy and basic setup
COPY . /var/www/html/
RUN chown -R www-data:www-data /var/www/html
# Test Laravel commands
RUN if [ ! -f .env ]; then cp .env.example .env; fi || echo ".env creation failed"
RUN composer install --no-dev --no-interaction || echo "composer install failed"
RUN php artisan key:generate || echo "key generation failed"
EXPOSE 80
CMD ["apache2-foreground"]

100
PHP_COMPATIBILITY.md Normal file
View File

@@ -0,0 +1,100 @@
# Laravel 5.0 and PHP Compatibility Guide
## Issue: mcrypt Extension Required
Laravel 5.0 requires the `mcrypt` PHP extension, which was:
- **Deprecated** in PHP 7.1
- **Removed** in PHP 7.2+
## Solutions
### Option 1: Use PHP 5.6 (Current Implementation)
The `Dockerfile.minimal` has been updated to use PHP 5.6 which is the ideal match for Laravel 5.0.
**Pros:**
- Perfect compatibility with Laravel 5.0
- Native mcrypt support
- All packages available and tested
- Matches original development environment
**Cons:**
- PHP 5.6 reached end-of-life in December 2018
- Security concerns for long-term production use
### Option 2: Upgrade Laravel (Recommended for Production)
Upgrade to Laravel 5.5+ which doesn't require mcrypt.
```bash
# Update composer.json
"laravel/framework": "5.8.*"
# Remove mcrypt dependencies and update encryption
php artisan key:generate
```
### Option 3: Use OpenSSL Instead (Laravel 5.2+)
If upgrading Laravel, update encryption configuration:
```php
// config/app.php
'cipher' => 'AES-256-CBC',
```
## Current Docker Configuration
The Dockerfile now uses:
- **Base Image**: `php:5.6-apache`
- **mcrypt Extension**: Native support (no installation issues)
- **Other Extensions**: All Laravel 5.0 requirements met
- **Package Compatibility**: Perfect match for PHP 5.6
## Production Recommendations
For a production deployment, consider:
1. **Upgrade Laravel** to 5.8 or later (LTS)
2. **Use PHP 7.4+** for better performance and security
3. **Replace mcrypt** with OpenSSL encryption
## Quick Upgrade Path (Optional)
If you want to modernize the application:
### Step 1: Update composer.json
```json
{
"require": {
"laravel/framework": "5.8.*"
}
}
```
### Step 2: Update Dockerfile to use PHP 7.4
```dockerfile
FROM php:7.4-apache
# Remove mcrypt installation
```
### Step 3: Update configuration
```bash
php artisan key:generate
php artisan config:cache
```
## Security Note
Since PHP 7.1 is end-of-life, monitor for security updates and consider upgrading the Laravel version for long-term production use.
## Testing the Current Setup
The current configuration should work with:
```bash
./start-local.sh
```
This will use PHP 7.1 with mcrypt support for Laravel 5.0 compatibility.

View File

@@ -405,7 +405,7 @@ class PaypalController extends Controller
if (count($grouped_item) <= 0) { if (count($grouped_item) <= 0) {
$tax_value = 0; $tax_value = 0;
} else { } else {
if ($grouped_item[0]->StoreId == 76 || $grouped_item[0]->StoreId == 78 || $grouped_item[0]->StoreId == 111 || $grouped_item[0]->StoreId == 131 || $grouped_item[0]->StoreId == 30 || $grouped_item[0]->StoreId == 141 || $grouped_item[0]->StoreId == 162 || $grouped_item[0]->StoreId == 185 || $grouped_item[0]->StoreId == 244 || $grouped_item[0]->StoreId == 301 || $grouped_item[0]->StoreId == 1 || $grouped_item[0]->StoreId == 329 || $grouped_item[0]->StoreId == 340) { if ($grouped_item[0]->StoreId == 76 || $grouped_item[0]->StoreId == 78 || $grouped_item[0]->StoreId == 111 || $grouped_item[0]->StoreId == 131 || $grouped_item[0]->StoreId == 30 || $grouped_item[0]->StoreId == 141 || $grouped_item[0]->StoreId == 162 || $grouped_item[0]->StoreId == 185 || $grouped_item[0]->StoreId == 244 || $grouped_item[0]->StoreId == 301 || $grouped_item[0]->StoreId == 1 || $grouped_item[0]->StoreId == 329 || $grouped_item[0]->StoreId == 340 || $grouped_item[0]->StoreId == 346) {
$tax_value = 0; $tax_value = 0;
} else { } else {
$tax_value = 0.10; $tax_value = 0.10;

156
cloudbuild.yaml Normal file
View File

@@ -0,0 +1,156 @@
# Google Cloud Build configuration for Laravel 5 application deployment to Cloud Run
steps:
# Step 1: Build the Docker image
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'gcr.io/$PROJECT_ID/merchbay-laravel:$COMMIT_SHA'
- '-t'
- 'gcr.io/$PROJECT_ID/merchbay-laravel:latest'
- '-f'
- 'Dockerfile.simple'
- '.'
id: 'build-image'
# Step 2: Push the Docker image to Google Container Registry
- name: 'gcr.io/cloud-builders/docker'
args:
- 'push'
- 'gcr.io/$PROJECT_ID/merchbay-laravel:$COMMIT_SHA'
id: 'push-image'
waitFor: ['build-image']
# Step 3: Push the latest tag as well
- name: 'gcr.io/cloud-builders/docker'
args:
- 'push'
- 'gcr.io/$PROJECT_ID/merchbay-laravel:latest'
id: 'push-latest'
waitFor: ['build-image']
# Step 4: Run database migrations (optional - only if you have Cloud SQL configured)
# Uncomment and configure if you need to run migrations
# - name: 'gcr.io/cloud-builders/gcloud'
# entrypoint: 'bash'
# args:
# - '-c'
# - |
# gcloud run jobs create laravel-migrate \
# --image=gcr.io/$PROJECT_ID/merchbay-laravel:$COMMIT_SHA \
# --region=$_REGION \
# --set-env-vars="APP_ENV=production,APP_KEY=$_APP_KEY,DB_HOST=$_DB_HOST,DB_DATABASE=$_DB_DATABASE,DB_USERNAME=$_DB_USERNAME,DB_PASSWORD=$_DB_PASSWORD" \
# --set-cloudsql-instances=$_CLOUDSQL_INSTANCE \
# --command="php" \
# --args="artisan,migrate,--force" \
# --max-retries=1 \
# --replace || true
# gcloud run jobs execute laravel-migrate --region=$_REGION --wait
# id: 'run-migrations'
# waitFor: ['push-image']
# Step 5: Deploy to Cloud Run
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- '$_SERVICE_NAME'
- '--image'
- 'gcr.io/$PROJECT_ID/merchbay-laravel:$COMMIT_SHA'
- '--region'
- '$_REGION'
- '--platform'
- 'managed'
- '--allow-unauthenticated'
- '--port'
- '8080'
- '--memory'
- '$_MEMORY'
- '--cpu'
- '$_CPU'
- '--timeout'
- '$_TIMEOUT'
- '--concurrency'
- '$_CONCURRENCY'
- '--min-instances'
- '$_MIN_INSTANCES'
- '--max-instances'
- '$_MAX_INSTANCES'
- '--set-env-vars'
- 'APP_ENV=production,APP_DEBUG=false,APP_KEY=$_APP_KEY,DB_HOST=$_DB_HOST,DB_DATABASE=$_DB_DATABASE,DB_USERNAME=$_DB_USERNAME,DB_PASSWORD=$_DB_PASSWORD,CACHE_DRIVER=redis,SESSION_DRIVER=redis,QUEUE_DRIVER=database,PAYPAL_MODE=$_PAYPAL_MODE,PAYPAL_LIVE_CLIENT_ID=$_PAYPAL_LIVE_CLIENT_ID,PAYPAL_LIVE_SECRET=$_PAYPAL_LIVE_SECRET,PAYPAL_SANDBOX_CLIENT_ID=$_PAYPAL_SANDBOX_CLIENT_ID,PAYPAL_SANDBOX_SECRET=$_PAYPAL_SANDBOX_SECRET,MAIL_DRIVER=$_MAIL_DRIVER,MAIL_HOST=$_MAIL_HOST,MAIL_PORT=$_MAIL_PORT,MAIL_USERNAME=$_MAIL_USERNAME,MAIL_PASSWORD=$_MAIL_PASSWORD,GOOGLE_ANALYTICS_VIEW_ID=$_GOOGLE_ANALYTICS_VIEW_ID'
- '--set-cloudsql-instances'
- '$_CLOUDSQL_INSTANCE'
- '--vpc-connector'
- '$_VPC_CONNECTOR'
- '--add-cloudsql-instances'
- '$_CLOUDSQL_INSTANCE'
id: 'deploy-service'
waitFor: ['push-image']
# Substitution variables - you can override these in your Cloud Build trigger
substitutions:
# Service configuration
_SERVICE_NAME: 'merchbay-laravel'
_REGION: 'us-central1'
# Resource limits
_MEMORY: '1Gi'
_CPU: '1000m'
_TIMEOUT: '300s'
_CONCURRENCY: '80'
_MIN_INSTANCES: '0'
_MAX_INSTANCES: '10'
# Database configuration (Cloud SQL)
_CLOUDSQL_INSTANCE: 'YOUR_PROJECT_ID:REGION:INSTANCE_NAME'
_DB_HOST: '127.0.0.1'
_DB_DATABASE: 'merchbay'
_DB_USERNAME: 'laravel_user'
_DB_PASSWORD: 'YOUR_DB_PASSWORD'
# VPC configuration (if needed)
_VPC_CONNECTOR: 'projects/YOUR_PROJECT_ID/locations/REGION/connectors/CONNECTOR_NAME'
# Application configuration
_APP_KEY: 'base64:YOUR_APP_KEY_HERE'
# PayPal configuration
_PAYPAL_MODE: 'live'
_PAYPAL_LIVE_CLIENT_ID: 'YOUR_PAYPAL_LIVE_CLIENT_ID'
_PAYPAL_LIVE_SECRET: 'YOUR_PAYPAL_LIVE_SECRET'
_PAYPAL_SANDBOX_CLIENT_ID: 'YOUR_PAYPAL_SANDBOX_CLIENT_ID'
_PAYPAL_SANDBOX_SECRET: 'YOUR_PAYPAL_SANDBOX_SECRET'
# Mail configuration
_MAIL_DRIVER: 'smtp'
_MAIL_HOST: 'smtp.gmail.com'
_MAIL_PORT: '587'
_MAIL_USERNAME: 'your-email@domain.com'
_MAIL_PASSWORD: 'your-app-password'
# Google Analytics
_GOOGLE_ANALYTICS_VIEW_ID: 'YOUR_GA_VIEW_ID'
# Build options
options:
# Use a more powerful machine for faster builds
machineType: 'E2_HIGHCPU_8'
# Enable detailed logging
logging: CLOUD_LOGGING_ONLY
# Build timeout
timeout: '1200s'
# Store build logs in Cloud Logging
logsBucket: 'gs://YOUR_PROJECT_ID_cloudbuild-logs'
# Available logs for debugging
availableSecrets:
secretManager:
- versionName: 'projects/YOUR_PROJECT_ID/secrets/APP_KEY/versions/latest'
env: 'APP_KEY'
- versionName: 'projects/YOUR_PROJECT_ID/secrets/DB_PASSWORD/versions/latest'
env: 'DB_PASSWORD'
- versionName: 'projects/YOUR_PROJECT_ID/secrets/PAYPAL_LIVE_SECRET/versions/latest'
env: 'PAYPAL_LIVE_SECRET'

View File

@@ -46,6 +46,9 @@
] ]
}, },
"config": { "config": {
"preferred-install": "dist" "preferred-install": "dist",
"allow-plugins": {
"kylekatarnls/update-helper": true
}
} }
} }

75
docker-compose.local.yml Normal file
View File

@@ -0,0 +1,75 @@
# Docker Compose for local development and testing
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.basic
ports:
- "8080:80"
environment:
- APP_ENV=local
- APP_DEBUG=true
- APP_KEY=base64:your-app-key-here
- DB_HOST=mysql
- DB_DATABASE=merchbay
- DB_USERNAME=laravel_user
- DB_PASSWORD=secret
- CACHE_DRIVER=file
- SESSION_DRIVER=file
- REDIS_HOST=redis
- QUEUE_DRIVER=sync
- PAYPAL_MODE=sandbox
- PAYPAL_SANDBOX_CLIENT_ID=your-sandbox-client-id
- PAYPAL_SANDBOX_SECRET=your-sandbox-secret
- MAIL_DRIVER=log
volumes:
- ./storage:/var/www/html/storage
- ./bootstrap/cache:/var/www/html/bootstrap/cache
depends_on:
- mysql
- redis
networks:
- app-network
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=merchbay
- MYSQL_USER=laravel_user
- MYSQL_PASSWORD=secret
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- app-network
phpmyadmin:
image: phpmyadmin/phpmyadmin
environment:
- PMA_HOST=mysql
- PMA_USER=root
- PMA_PASSWORD=root
ports:
- "8081:80"
depends_on:
- mysql
networks:
- app-network
volumes:
mysql_data:
networks:
app-network:
driver: bridge

View File

@@ -3,37 +3,29 @@ server {
index index.php index.html; index index.php index.html;
error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
root /var/www; root /var/www/public;
index index.php index.html;
location / { location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, HEAD';
add_header 'Access-Control-Max-Age' '1728000';
add_header 'Access-Control-Allow-Headers' '*';
#add_header 'Content-Type: text/plain; charset=UTF-8';
#add_header 'Content-Length: 0';
return 204;
}
try_files $uri $uri/ /index.php?$query_string; try_files $uri $uri/ /index.php?$query_string;
} }
location ~ \.php$ { location ~ \.php$ {
fastcgi_index index.php; try_files $uri =404;
fastcgi_pass app:9000;
include fastcgi_params;
fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_pass app:9000;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info; fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
} }
location ~ /\.ht {
deny all;
}
location ~* \.(css|less|js|jpg|png|gif)$ { location ~* \.(css|less|js|jpg|png|gif)$ {
add_header Cache-Control "no-cache, no-store, must-revalidate"; expires 0;
add_header Pragma "no-cache"; add_header Cache-Control "no-cache, no-store, must-revalidate";
expires 0; add_header Pragma "no-cache";
} }
} }

106
rebuild.sh Executable file
View File

@@ -0,0 +1,106 @@
#!/bin/bash
# Check if docker compose (new) or docker-compose (old) is available
if command -v docker &>/dev/null; then
if docker compose version &>/dev/null; then
DOCKER_COMPOSE="docker compose"
elif command -v docker-compose &>/dev/null; then
DOCKER_COMPOSE="docker-compose"
else
echo "❌ Error: Neither 'docker compose' nor 'docker-compose' found."
echo "Please install Docker Desktop from https://www.docker.com/products/docker-desktop/"
exit 1
fi
else
echo "❌ Error: Docker is not installed."
echo "Please install Docker Desktop from https://www.docker.com/products/docker-desktop/"
exit 1
fi
echo "🚀 Starting rebuild process..."
# Function to display usage
show_usage() {
echo "Usage: ./rebuild.sh [OPTIONS]"
echo "Options:"
echo " -h, --help Show this help message"
echo " -c, --clean Perform a clean rebuild (no cache)"
echo " -f, --fresh Perform a fresh migration"
}
# Default values
CLEAN_BUILD=false
FRESH_MIGRATION=false
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-h|--help) show_usage; exit 0 ;;
-c|--clean) CLEAN_BUILD=true ;;
-f|--fresh) FRESH_MIGRATION=true ;;
*) echo "Unknown parameter: $1"; show_usage; exit 1 ;;
esac
shift
done
# Pull latest changes from git
echo "📥 Pulling latest changes from git..."
if ! git pull; then
echo "❌ Git pull failed. Please resolve any conflicts and try again."
exit 1
fi
# Stop running containers
echo "📥 Stopping running containers..."
$DOCKER_COMPOSE down
# Remove old images if clean build
if [ "$CLEAN_BUILD" = true ] ; then
echo "🧹 Cleaning Docker cache..."
$DOCKER_COMPOSE rm -f
docker system prune -f
fi
# Build and start containers
echo "🏗️ Building containers..."
if [ "$CLEAN_BUILD" = true ] ; then
$DOCKER_COMPOSE build --no-cache
else
$DOCKER_COMPOSE build
fi
# Start containers
echo "🚀 Starting containers..."
$DOCKER_COMPOSE up -d
# Wait for the containers to be ready
echo "⏳ Waiting for containers to be ready..."
sleep 10
# Clear Laravel cache
echo "🧹 Clearing Laravel cache..."
$DOCKER_COMPOSE exec app php artisan cache:clear
$DOCKER_COMPOSE exec app php artisan config:clear
$DOCKER_COMPOSE exec app php artisan view:clear
# Run composer install
echo "📦 Installing dependencies..."
$DOCKER_COMPOSE exec app composer install
# Generate application key if .env exists and APP_KEY is empty
if [ -f .env ] && ! grep -q "^APP_KEY=[A-Za-z0-9+/]\{40\}$" .env; then
echo "🔑 Generating application key..."
$DOCKER_COMPOSE exec app php artisan key:generate
fi
# Run migrations if requested
if [ "$FRESH_MIGRATION" = true ] ; then
echo "🔄 Running fresh migrations..."
$DOCKER_COMPOSE exec app php artisan migrate:fresh
else
echo "🔄 Running migrations..."
$DOCKER_COMPOSE exec app php artisan migrate
fi
echo "✨ Rebuild completed!"
echo "🌐 Your application should be available at http://localhost:8080"

View File

@@ -10,11 +10,11 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Merchbay</title> <title>Merchbay</title>
<link rel="icon" href="{{ asset('public/favicon.ico') }}"> <link rel="icon" href="{{ asset('favicon.ico') }}">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;400;500;600;700;800;900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="{{ asset('public/assets/css/merchbay/styles.css') }}" rel="stylesheet"> <link href="{{ asset('assets/css/merchbay/styles.css') }}" rel="stylesheet">
<!-- <link href="{{ asset('public/assets/login/css/style.css') }}" rel="stylesheet"> <!-- <link href="{{ asset('public/assets/login/css/style.css') }}" rel="stylesheet">
<link href="{{ asset('public/assets/login/css/form-elements.css') }}" rel="stylesheet"> --> <link href="{{ asset('public/assets/login/css/form-elements.css') }}" rel="stylesheet"> -->
<script src='https://www.google.com/recaptcha/api.js'></script> <script src='https://www.google.com/recaptcha/api.js'></script>
@@ -258,7 +258,7 @@
} }
function fetchCanada() { function fetchCanada() {
$.getJSON("{{ asset('/public/api/canada.json') }}", function(items) { $.getJSON("{{ asset('/api/canada.json') }}", function(items) {
var states = []; var states = [];
Object.keys(items).forEach(function(state) { Object.keys(items).forEach(function(state) {
@@ -307,7 +307,7 @@
} }
function fetchUSA() { function fetchUSA() {
$.getJSON("{{ asset('/public/api/usaCities.json') }}", function(data) { $.getJSON("{{ asset('/api/usaCities.json') }}", function(data) {
var states = []; var states = [];
for (i = 0; i < data.length; i++) { for (i = 0; i < data.length; i++) {

66
start-local.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
# Quick start script for local development and testing before Cloud Run deployment
set -e
echo "🚀 Starting Merchbay Laravel Application Setup..."
# Check if Docker is running
if ! docker info > /dev/null 2>&1; then
echo "❌ Docker is not running. Please start Docker and try again."
exit 1
fi
# Determine which docker compose command to use
if command -v docker-compose > /dev/null 2>&1; then
DOCKER_COMPOSE="docker-compose"
elif docker compose version > /dev/null 2>&1; then
DOCKER_COMPOSE="docker compose"
else
echo "❌ Neither 'docker-compose' nor 'docker compose' is available."
echo "Please install Docker Compose and try again."
exit 1
fi
echo " Using: $DOCKER_COMPOSE"
# Check if .env file exists
if [ ! -f .env ]; then
echo "📄 Creating .env file from .env.example..."
cp .env.example .env
echo "✅ .env file created. Please update it with your configuration."
fi
# Build and start services
echo "🔨 Building Docker images..."
$DOCKER_COMPOSE -f docker-compose.local.yml build
echo "🚀 Starting services..."
$DOCKER_COMPOSE -f docker-compose.local.yml up -d
echo "⏳ Waiting for services to be ready..."
sleep 30
# Run Laravel setup commands
echo "🔧 Setting up Laravel..."
$DOCKER_COMPOSE -f docker-compose.local.yml exec app php artisan key:generate
#$DOCKER_COMPOSE -f docker-compose.local.yml exec app php artisan migrate --force
$DOCKER_COMPOSE -f docker-compose.local.yml exec app php artisan config:cache
$DOCKER_COMPOSE -f docker-compose.local.yml exec app php artisan route:cache
$DOCKER_COMPOSE -f docker-compose.local.yml exec app php artisan view:cache
echo "✅ Setup complete!"
echo ""
echo "🌐 Your application is now running at:"
echo " Application: http://localhost:8080"
echo " phpMyAdmin: http://localhost:8081"
echo ""
echo "📊 Check logs with:"
echo " $DOCKER_COMPOSE -f docker-compose.local.yml logs -f app"
echo ""
echo "🛑 Stop the application with:"
echo " $DOCKER_COMPOSE -f docker-compose.local.yml down"
echo ""
echo "🚀 When ready to deploy to Cloud Run, use:"
echo " gcloud builds submit --config=cloudbuild.yaml"