diff --git a/NGINX_SETUP.md b/NGINX_SETUP.md new file mode 100644 index 00000000..474e517e --- /dev/null +++ b/NGINX_SETUP.md @@ -0,0 +1,131 @@ +# OpnForm Nginx Setup Guide + +This guide explains how to set up OpnForm with a host-level nginx configuration. + +## Architecture Overview + +The modified setup removes the main nginx ingress container and exposes services directly: + +- **UI Service**: Exposed on port 7655 (HTTP) +- **API Service**: Exposed on port 7654 (HTTP via minimal nginx container) +- **Database**: PostgreSQL (internal only) +- **Redis**: Cache service (internal only) + +## Key Changes from Default Setup + +1. **Removed YAML anchors** - Each container now has its own explicit configuration to avoid conflicts +2. **Removed main ingress container** - Your host nginx handles all routing +3. **Added minimal api-nginx** - Small nginx container just to convert FastCGI to HTTP for the API +4. **Custom ports** - Using 7654-7655 range to avoid conflicts + +## Setup Steps + +### 1. Stop any existing containers + +```bash +docker compose down +docker compose -f docker-compose.dev.yml down +``` + +### 2. Run the setup script + +```bash +./scripts/docker-setup.sh +``` + +### 3. Verify services are running + +```bash +docker compose ps +``` + +You should see: +- opnform-api (healthy) +- opnform-api-nginx (healthy) +- opnform-api-worker (running) +- opnform-api-scheduler (running) +- opnform-client (healthy) +- opnform-redis (healthy) +- opnform-db (healthy) + +### 4. Configure your host nginx + +Copy the example configuration: + +```bash +sudo cp nginx-host-example.conf /etc/nginx/sites-available/forms.portnimara.dev +sudo ln -s /etc/nginx/sites-available/forms.portnimara.dev /etc/nginx/sites-enabled/ +``` + +Edit the file to adjust: +- SSL certificate paths +- Server name if different +- Any other site-specific settings + +### 5. Test nginx configuration + +```bash +sudo nginx -t +``` + +### 6. Reload nginx + +```bash +sudo systemctl reload nginx +``` + +## Troubleshooting + +### Port already in use + +If you get "port already allocated" errors: + +1. Check what's using the ports: + ```bash + sudo lsof -i :7654 + sudo lsof -i :7655 + ``` + +2. Stop conflicting services or change the ports in docker-compose.yml + +### API not responding + +1. Check the api-nginx logs: + ```bash + docker logs opnform-api-nginx + ``` + +2. Verify the API container is running: + ```bash + docker logs opnform-api + ``` + +### UI not loading + +1. Check the client logs: + ```bash + docker logs opnform-client + ``` + +2. Ensure the client/.env file has correct API URL settings + +## Port Reference + +- **7654**: API (HTTP) - proxied through api-nginx to PHP-FPM +- **7655**: UI (HTTP) - Nuxt.js frontend +- **9000**: PHP-FPM (internal only, FastCGI protocol) +- **5432**: PostgreSQL (internal only) +- **6379**: Redis (internal only) + +## Security Notes + +1. Ports are bound to 127.0.0.1 only, not exposed to external network +2. All traffic should go through your host nginx with SSL +3. The minimal api-nginx container only handles FastCGI conversion, no SSL termination + +## Default Credentials + +- Email: admin@opnform.com +- Password: password + +**Important**: Change these immediately after first login! diff --git a/docker-compose.yml b/docker-compose.yml index c1ca2438..13803b8e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,10 @@ ---- services: - api: &api-environment + api: image: jhumanj/opnform-api:latest container_name: opnform-api - volumes: &api-environment-volumes + volumes: - opnform_storage:/usr/share/nginx/html/storage:rw - environment: &api-env + environment: APP_ENV: production # Database settings DB_HOST: db @@ -25,7 +24,7 @@ services: db: condition: service_healthy redis: - condition: service_healthy # Depend on redis being healthy too + condition: service_healthy healthcheck: test: ["CMD-SHELL", "php /usr/share/nginx/html/artisan about || exit 1"] interval: 30s @@ -33,13 +32,49 @@ services: retries: 3 start_period: 60s + api-nginx: + image: nginx:alpine + container_name: opnform-api-nginx + volumes: + - ./docker/api-nginx.conf:/etc/nginx/nginx.conf:ro + - opnform_storage:/usr/share/nginx/html/storage:ro + ports: + - "127.0.0.1:7654:80" # API on port 7654 + depends_on: + - api + healthcheck: + test: ["CMD-SHELL", "wget --spider -q http://localhost/ || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + api-worker: - <<: *api-environment + image: jhumanj/opnform-api:latest container_name: opnform-api-worker command: ["php", "artisan", "queue:work"] + volumes: + - opnform_storage:/usr/share/nginx/html/storage:rw environment: - <<: *api-env APP_ENV: production + # Database settings + DB_HOST: db + REDIS_HOST: redis + DB_DATABASE: ${DB_DATABASE:-forge} + DB_USERNAME: ${DB_USERNAME:-forge} + DB_PASSWORD: ${DB_PASSWORD:-forge} + DB_CONNECTION: ${DB_CONNECTION:-pgsql} + # PHP Configuration + PHP_MEMORY_LIMIT: "1G" + PHP_MAX_EXECUTION_TIME: "600" + PHP_UPLOAD_MAX_FILESIZE: "64M" + PHP_POST_MAX_SIZE: "64M" + env_file: + - ./api/.env + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy healthcheck: test: ["CMD-SHELL", "pgrep -f 'php artisan queue:work' > /dev/null || exit 1"] interval: 60s @@ -48,22 +83,44 @@ services: start_period: 30s api-scheduler: - <<: *api-environment + image: jhumanj/opnform-api:latest container_name: opnform-api-scheduler command: ["php", "artisan", "schedule:work"] + volumes: + - opnform_storage:/usr/share/nginx/html/storage:rw environment: - <<: *api-env APP_ENV: production + # Database settings + DB_HOST: db + REDIS_HOST: redis + DB_DATABASE: ${DB_DATABASE:-forge} + DB_USERNAME: ${DB_USERNAME:-forge} + DB_PASSWORD: ${DB_PASSWORD:-forge} + DB_CONNECTION: ${DB_CONNECTION:-pgsql} + # PHP Configuration + PHP_MEMORY_LIMIT: "1G" + PHP_MAX_EXECUTION_TIME: "600" + PHP_UPLOAD_MAX_FILESIZE: "64M" + PHP_POST_MAX_SIZE: "64M" + env_file: + - ./api/.env + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy healthcheck: test: ["CMD-SHELL", "php /usr/share/nginx/html/artisan app:scheduler-status --mode=check --max-minutes=3 || exit 1"] interval: 60s timeout: 30s retries: 3 - start_period: 70s # Allow time for first scheduled run and cache write + start_period: 70s ui: image: jhumanj/opnform-client:latest container_name: opnform-client + ports: + - "127.0.0.1:7655:3000" # UI on port 7655 env_file: - ./client/.env healthcheck: @@ -95,27 +152,6 @@ services: volumes: - postgres-data:/var/lib/postgresql/data - ingress: - image: nginx:1 - container_name: opnform-ingress - volumes: - - ./docker/nginx.conf:/etc/nginx/templates/default.conf.template - ports: - - 80:80 - environment: - - NGINX_MAX_BODY_SIZE=64m - depends_on: - api: - condition: service_started - ui: - condition: service_started - healthcheck: - test: ["CMD-SHELL", "nginx -t && curl -f http://localhost/ || exit 1"] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - volumes: postgres-data: - opnform_storage: \ No newline at end of file + opnform_storage: diff --git a/docker/api-nginx.conf b/docker/api-nginx.conf new file mode 100644 index 00000000..8294d37e --- /dev/null +++ b/docker/api-nginx.conf @@ -0,0 +1,43 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + server { + listen 80; + server_name _; + root /usr/share/nginx/html/public; + index index.php; + + client_max_body_size 64M; + + # Logging + access_log /dev/stdout; + error_log /dev/stderr; + + # Handle all requests through PHP + location / { + try_files $uri $uri/ /index.php$is_args$args; + } + + # PHP-FPM configuration + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass opnform-api:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php; + fastcgi_param DOCUMENT_ROOT /usr/share/nginx/html/public; + fastcgi_param REQUEST_URI $request_uri; + fastcgi_read_timeout 300; + } + + # Deny access to . files + location ~ /\. { + deny all; + } + } +} diff --git a/nginx-host-example.conf b/nginx-host-example.conf new file mode 100644 index 00000000..680dde73 --- /dev/null +++ b/nginx-host-example.conf @@ -0,0 +1,67 @@ +# Example nginx configuration for forms.portnimara.dev +# Place this in /etc/nginx/sites-available/forms.portnimara.dev +# Then create a symlink: ln -s /etc/nginx/sites-available/forms.portnimara.dev /etc/nginx/sites-enabled/ + +server { + listen 80; + server_name forms.portnimara.dev; + + # Redirect HTTP to HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name forms.portnimara.dev; + + # SSL certificates - adjust paths as needed + ssl_certificate /etc/letsencrypt/live/forms.portnimara.dev/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/forms.portnimara.dev/privkey.pem; + + # SSL configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Client upload size + client_max_body_size 64M; + + # Logging + access_log /var/log/nginx/forms.portnimara.dev.access.log; + error_log /var/log/nginx/forms.portnimara.dev.error.log; + + # API routes - proxy to the api-nginx container + location ~ ^/(api|open|local/temp|forms/assets)/ { + proxy_pass http://127.0.0.1:7654; + 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; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_read_timeout 300s; + proxy_connect_timeout 75s; + } + + # Everything else goes to the UI container + location / { + proxy_pass http://127.0.0.1:7655; + proxy_http_version 1.1; + 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; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + + # WebSocket support for hot reload and real-time features + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_cache_bypass $http_upgrade; + } + + # 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; +} diff --git a/scripts/docker-setup.sh b/scripts/docker-setup.sh index 1f3b765f..ef5dc5fb 100755 --- a/scripts/docker-setup.sh +++ b/scripts/docker-setup.sh @@ -63,9 +63,12 @@ if [ "$DEV_MODE" = true ]; then else echo -e "${BLUE}Production environment setup complete!${NC}" echo -e "${YELLOW}Please wait a moment for all services to start${NC}" - echo -e "${GREEN}Then visit: http://localhost${NC}" + echo -e "${GREEN}Services are available on:${NC}" + echo -e "${GREEN}- UI: http://localhost:7655${NC}" + echo -e "${GREEN}- API: http://localhost:7654${NC}" + echo -e "${YELLOW}Note: Configure your host nginx to proxy to these ports${NC}" fi echo -e "${BLUE}Default admin credentials:${NC}" echo -e "${GREEN}Email: admin@opnform.com${NC}" -echo -e "${GREEN}Password: password${NC}" +echo -e "${GREEN}Password: password${NC}"