ShulNET Community Edition Forums

Welcome!

This community is for professionals and enthusiasts of our products and services.
Share and discuss the best content and new marketing ideas, build your professional profile and become a better marketer together.

0

Using the Docker Image

אווטר
Administrator

Docker Deployment Guide

This guide covers how to deploy ShulNET using Docker in various environments, from local development to production deployments.

Table of Contents

Quick Start (Development)

Prerequisites
  • Docker Desktop or Docker Engine 20.10+
  • Docker Compose v2.0+
  • Git
Local Development Setup
  1. Clone the repository
    git clone 
    cd shulnet-php
    
  2. Configure environment
    cp .env.example .env
    # Edit .env to set your environment variables
    
  3. Start the stack
    docker-compose up -d
    
  4. Run initial setup
    # Generate application key
    docker-compose exec app php artisan key:generate
    
    # Run migrations
    docker-compose exec app php artisan migrate
    
    # Seed the database (optional)
    docker-compose exec app php artisan db:seed
    
    # Create storage link
    docker-compose exec app php artisan storage:link
    
  5. Access the application

Production Deployment

Building the Production Image

The Dockerfile uses multi-stage builds for optimization:

# Build production image
docker build --target app -t shulnet-php:latest .

# Or with version tag
docker build --target app -t shulnet-php:v1.0.0 .

# Push to registry
docker tag shulnet-php:latest your-registry.com/shulnet-php:latest
docker push your-registry.com/shulnet-php:latest
Production Environment Variables

Create a .env.production file (DO NOT commit to version control):

# Application
APP_NAME=ShulNET
APP_ENV=production
APP_KEY=base64:YOUR_GENERATED_KEY_HERE
APP_DEBUG=false
APP_URL=https://your-domain.com

# Database
DB_CONNECTION=mysql
DB_HOST=your-db-host
DB_PORT=3306
DB_DATABASE=shulnet_production
DB_USERNAME=shulnet_user
DB_PASSWORD=STRONG_PASSWORD_HERE

# Cache & Session
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

# Redis
REDIS_HOST=your-redis-host
REDIS_PASSWORD=REDIS_PASSWORD_HERE
REDIS_PORT=6379

# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.your-provider.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=EMAIL_PASSWORD_HERE
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"

# AWS S3 (if using for file storage)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
FILESYSTEM_DISK=s3

Docker Image Architecture

Multi-Stage Build

The Dockerfile consists of three stages:

  1. Base Stage - Common dependencies
    • PHP 8.4 FPM
    • System packages (nginx, supervisor, image libraries)
    • PHP extensions (pdo_mysql, gd, bcmath, zip, etc.)
  2. Vendor Stage - Dependency installation
    • Composer dependencies (production-only)
    • Optimized for caching
  3. App Stage - Final production image
    • Application code
    • Optimized autoloader
    • Nginx configuration
    • Supervisor configuration
    • Proper permissions for www-data
Container Components

The production container runs three services via Supervisor:

  • PHP-FPM (port 9000) - PHP application server
  • Nginx (port 80) - Web server
  • Laravel Queue Worker - Background job processing
Image Size Optimization
  • Multi-stage builds reduce final image size
  • No dev dependencies in production
  • Optimized autoloader with --classmap-authoritative
  • Clean apt cache after installations

Environment Configuration

Docker Compose Services
App Service
app:
  image: parkerfly38/shulnet-php:latest
  # Database and Redis must be available
  # Exposes port 8000 for development (80 internally)
Database Service
db:
  image: mysql:8.0
  # Persistent volume: dbdata
  # Port 3307 externally, 3306 internally
Redis Service
redis:
  image: redis:alpine
  # Port 6380 externally, 6379 internally
Node Service (Development)
node:
  image: node:20-alpine
  # Runs Vite dev server on port 5173
  # For asset compilation
Volume Mounts

Development:

volumes:
  - ./:/var/www/html  # Live code reload
  - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini

Production:

volumes:
  - storage:/var/www/html/storage  # Persistent storage
  - logs:/var/www/html/storage/logs  # Log files

Integration with External Services

Reverse Proxy (Nginx/Traefik/HAProxy)
With Traefik
version: '3.8'

services:
  app:
    image: parkerfly38/shulnet-php:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.shulnet.rule=Host(`shulnet.example.com`)"
      - "traefik.http.routers.shulnet.entrypoints=websecure"
      - "traefik.http.routers.shulnet.tls.certresolver=letsencrypt"
      - "traefik.http.services.shulnet.loadbalancer.server.port=80"
    networks:
      - traefik-public
      - backend

  db:
    image: mysql:8.0
    networks:
      - backend

  redis:
    image: redis:alpine
    networks:
      - backend

networks:
  traefik-public:
    external: true
  backend:
    internal: true
With Nginx Reverse Proxy
upstream shulnet_backend {
    server app:80;
}

server {
    listen 443 ssl http2;
    server_name shulnet.example.com;
    
    ssl_certificate /etc/ssl/certs/shulnet.crt;
    ssl_certificate_key /etc/ssl/private/shulnet.key;
    
    location / {
        proxy_pass http://shulnet_backend;
        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;
    }
}
External Database

To use an external MySQL/PostgreSQL database:

services:
  app:
    image: parkerfly38/shulnet-php:latest
    environment:
      - DB_CONNECTION=mysql
      - DB_HOST=external-db.example.com
      - DB_PORT=3306
      - DB_DATABASE=shulnet
      - DB_USERNAME=${DB_USERNAME}
      - DB_PASSWORD=${DB_PASSWORD}
    # Remove db service dependency
External Redis/Cache
services:
  app:
    image: parkerfly38/shulnet-php:latest
    environment:
      - REDIS_HOST=redis.example.com
      - REDIS_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
      - CACHE_STORE=redis
      - SESSION_DRIVER=redis
      - QUEUE_CONNECTION=redis
S3-Compatible Object Storage

For file uploads (CloudFlare R2, AWS S3, MinIO):

services:
  app:
    image: parkerfly38/shulnet-php:latest
    environment:
      - FILESYSTEM_DISK=s3
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - AWS_DEFAULT_REGION=us-east-1
      - AWS_BUCKET=shulnet-storage
      - AWS_ENDPOINT=https://storage.example.com  # For R2/MinIO
      - AWS_USE_PATH_STYLE_ENDPOINT=false

Kubernetes Deployment

Namespace and ConfigMap
apiVersion: v1
kind: Namespace
metadata:
  name: shulnet
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: shulnet-config
  namespace: shulnet
data:
  APP_NAME: "ShulNET"
  APP_ENV: "production"
  DB_CONNECTION: "mysql"
  CACHE_STORE: "redis"
  SESSION_DRIVER: "redis"
  QUEUE_CONNECTION: "redis"
Secrets
apiVersion: v1
kind: Secret
metadata:
  name: shulnet-secrets
  namespace: shulnet
type: Opaque
stringData:
  APP_KEY: "base64:YOUR_APP_KEY_HERE"
  DB_PASSWORD: "YOUR_DB_PASSWORD"
  DB_USERNAME: "shulnet_user"
  REDIS_PASSWORD: "YOUR_REDIS_PASSWORD"
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: shulnet-app
  namespace: shulnet
spec:
  replicas: 3
  selector:
    matchLabels:
      app: shulnet
  template:
    metadata:
      labels:
        app: shulnet
    spec:
      containers:
      - name: shulnet
        image: parkerfly38/shulnet-php:latest
        ports:
        - containerPort: 80
        envFrom:
        - configMapRef:
            name: shulnet-config
        - secretRef:
            name: shulnet-secrets
        env:
        - name: DB_HOST
          value: "mysql-service"
        - name: REDIS_HOST
          value: "redis-service"
        volumeMounts:
        - name: storage
          mountPath: /var/www/html/storage
        livenessProbe:
          httpGet:
            path: /api/health
            port: 80
          initialDelaySeconds: 40
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /api/health
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      volumes:
      - name: storage
        persistentVolumeClaim:
          claimName: shulnet-storage-pvc
Service
apiVersion: v1
kind: Service
metadata:
  name: shulnet-service
  namespace: shulnet
spec:
  selector:
    app: shulnet
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shulnet-ingress
  namespace: shulnet
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/proxy-body-size: "40m"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - shulnet.example.com
    secretName: shulnet-tls
  rules:
  - host: shulnet.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: shulnet-service
            port:
              number: 80
Persistent Volume Claim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shulnet-storage-pvc
  namespace: shulnet
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
  storageClassName: nfs-client  # Or your storage class
Database (MySQL StatefulSet)
apiVersion: v1
kind: Service
metadata:
  name: mysql-service
  namespace: shulnet
spec:
  ports:
  - port: 3306
  selector:
    app: mysql
  clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: shulnet
spec:
  serviceName: mysql-service
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: shulnet-secrets
              key: DB_PASSWORD
        - name: MYSQL_DATABASE
          value: shulnet
        - name: MYSQL_USER
          valueFrom:
            secretKeyRef:
              name: shulnet-secrets
              key: DB_USERNAME
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: shulnet-secrets
              key: DB_PASSWORD
        volumeMounts:
        - name: mysql-data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 20Gi
Redis Deployment
apiVersion: v1
kind: Service
metadata:
  name: redis-service
  namespace: shulnet
spec:
  ports:
  - port: 6379
  selector:
    app: redis
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
  namespace: shulnet
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:alpine
        ports:
        - containerPort: 6379
        command: ["redis-server", "--requirepass", "$(REDIS_PASSWORD)"]
        env:
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: shulnet-secrets
              key: REDIS_PASSWORD
Init Job (Migrations)
apiVersion: batch/v1
kind: Job
metadata:
  name: shulnet-migrate
  namespace: shulnet
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: parkerfly38/shulnet-php:latest
        command: ["php", "artisan", "migrate", "--force"]
        envFrom:
        - configMapRef:
            name: shulnet-config
        - secretRef:
            name: shulnet-secrets
        env:
        - name: DB_HOST
          value: "mysql-service"
        - name: REDIS_HOST
          value: "redis-service"
      restartPolicy: OnFailure

Docker Swarm/Compose Production Setup

Stack Deployment
version: '3.8'

services:
  app:
    image: parkerfly38/shulnet-php:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - node.role == worker
    environment:
      - DB_HOST=db
      - REDIS_HOST=redis
    secrets:
      - app_key
      - db_password
    networks:
      - frontend
      - backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/api/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 40s

  db:
    image: mysql:8.0
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.labels.database == true
    environment:
      - MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_password
      - MYSQL_DATABASE=shulnet
    secrets:
      - db_password
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend

  redis:
    image: redis:alpine
    deploy:
      replicas: 1
    networks:
      - backend

  nginx:
    image: nginx:alpine
    deploy:
      replicas: 2
    ports:
      - "80:80"
      - "443:443"
    configs:
      - source: nginx_config
        target: /etc/nginx/nginx.conf
    networks:
      - frontend

networks:
  frontend:
    driver: overlay
  backend:
    driver: overlay
    internal: true

volumes:
  db_data:
    driver: local

secrets:
  app_key:
    external: true
  db_password:
    external: true

configs:
  nginx_config:
    external: true
Deploy Commands
# Create secrets
echo "base64:YOUR_APP_KEY" | docker secret create app_key -
echo "YOUR_DB_PASSWORD" | docker secret create db_password -

# Deploy stack
docker stack deploy -c docker-compose.prod.yml shulnet

# Scale services
docker service scale shulnet_app=5

# Update service
docker service update --image parkerfly38/shulnet-php:v1.0.1 shulnet_app

# View logs
docker service logs -f shulnet_app

Troubleshooting

Common Issues
Container fails health check
# Check container logs
docker logs shulnet-app

# Check health endpoint manually
docker exec shulnet-app curl -f http://localhost/api/health

# Verify PHP-FPM is running
docker exec shulnet-app ps aux | grep php-fpm
Permission errors on storage
# Fix permissions
docker exec shulnet-app chown -R www-data:www-data /var/www/html/storage
docker exec shulnet-app chmod -R 775 /var/www/html/storage
Database connection issues
# Test database connectivity
docker exec shulnet-app php artisan tinker

>>> DB::connection()->getPdo();

# Check environment variables
docker exec shulnet-app env | grep DB_
Queue workers not processing jobs
# Check supervisor status
docker exec shulnet-app supervisorctl status

# Restart queue worker
docker exec shulnet-app supervisorctl restart laravel-worker:*

# View worker logs
docker exec shulnet-app tail -f /var/www/html/storage/logs/worker.log
High memory usage
# Check container stats
docker stats shulnet-app

# Adjust PHP memory limit in docker/php/local.ini
# Then rebuild image or mount updated config
Debugging
# Shell into container
docker exec -it shulnet-app bash

# View all supervisor logs
docker exec shulnet-app tail -f /var/log/supervisor/supervisord.log

# View nginx logs
docker exec shulnet-app tail -f /var/log/nginx.err.log

# View PHP-FPM logs
docker exec shulnet-app tail -f /var/log/php-fpm.err.log

# Laravel logs
docker exec shulnet-app tail -f /var/www/html/storage/logs/laravel.log
Performance Tuning
PHP-FPM Pool Configuration

Create docker/php/www.conf (mount as volume):

[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
Nginx Worker Configuration

Update docker/nginx/default.conf:

worker_processes auto;
worker_connections 1024;

http {
    keepalive_timeout 65;
    client_max_body_size 40M;
    
    # Enable gzip
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript;
}
MySQL Optimization

For production MySQL, add configuration:

db:
  image: mysql:8.0
  command: --default-authentication-plugin=mysql_native_password
    --max-connections=200
    --innodb-buffer-pool-size=1G
    --innodb-log-file-size=256M

Monitoring

Health Checks

The application includes a health check endpoint at /api/health that verifies:

  • Application is running
  • Database connectivity
  • Cache connectivity
Logging

Logs are available in multiple locations:

  • Application logs: /var/www/html/storage/logs/laravel.log
  • Worker logs: /var/www/html/storage/logs/worker.log
  • Nginx logs: /var/log/nginx.err.log and /var/log/nginx.access.log
  • PHP-FPM logs: /var/log/php-fpm.err.log
  • Supervisor logs: /var/log/supervisor/supervisord.log
External Monitoring Integration

Consider integrating with:

  • Application Performance Monitoring: New Relic, DataDog, Scout APM
  • Log Aggregation: ELK Stack, Splunk, Papertrail
  • Uptime Monitoring: Pingdom, UptimeRobot, StatusCake

Security Considerations

Best Practices
  1. Never commit secrets - Use .env files, Docker secrets, or environment variables
  2. Use TLS/SSL - Always encrypt traffic with HTTPS in production
  3. Regular updates - Keep base images and dependencies updated
  4. Limit container privileges - Run as non-root user (www-data)
  5. Network isolation - Use internal networks for database/cache
  6. Backup regularly - Automated backups of database and storage volumes
  7. Rate limiting - Implement at reverse proxy level
  8. Firewall rules - Only expose necessary ports
Image Scanning
# Scan for vulnerabilities
docker scan parkerfly38/shulnet-php:latest

# Or use Trivy
trivy image parkerfly38/shulnet-php:latest

Backup and Recovery

Database Backup
# Backup
docker exec shulnet-db mysqldump -u root -p${DB_PASSWORD} shulnet > backup.sql

# Restore
docker exec -i shulnet-db mysql -u root -p${DB_PASSWORD} shulnet < backup.sql
Storage Backup
# Backup storage volume
docker run --rm \
  -v shulnet_storage:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/storage-backup.tar.gz /data

# Restore
docker run --rm \
  -v shulnet_storage:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/storage-backup.tar.gz -C /

Additional Resources

For additional help or questions, please open an issue in the repository.

אווטר
בטל