diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 8922b4d6e..79d7799b3 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -1,37 +1,70 @@ -name: Publish Docker image +name: Publish Docker Images on: push: tags: - "v*" + branches: + - main + paths: + - "api/**" + - "client/**" + - "docker/**" + - "docker-compose*.yml" + workflow_dispatch: + +permissions: + contents: read + packages: write jobs: push_to_registry: - name: Push Docker image to Docker Hub + name: Push Docker images to Docker Hub runs-on: ubuntu-latest steps: - - name: Get tag name + - name: Get version info run: | - ( - echo "TAG_NAME=${GITHUB_REF#refs/*/v}"; - echo "DOCKER_UI_REPO=${{secrets.DOCKER_UI_REPO}}" - echo "DOCKER_API_REPO=${{secrets.DOCKER_API_REPO}}" - ) >> $GITHUB_ENV + if [[ $GITHUB_REF == refs/tags/* ]]; then + echo "VERSION=${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV + echo "API_TAGS=${{secrets.DOCKER_API_REPO}}:latest,${{secrets.DOCKER_API_REPO}}:${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV + echo "UI_TAGS=${{secrets.DOCKER_UI_REPO}}:latest,${{secrets.DOCKER_UI_REPO}}:${GITHUB_REF#refs/*/v}" >> $GITHUB_ENV + else + echo "VERSION=dev" >> $GITHUB_ENV + echo "API_TAGS=${{secrets.DOCKER_API_REPO}}:dev" >> $GITHUB_ENV + echo "UI_TAGS=${{secrets.DOCKER_UI_REPO}}:dev" >> $GITHUB_ENV + fi - name: Check out the repo uses: actions/checkout@v3 - - name: Log in to Docker Hub - run: docker login -u "${{ secrets.DOCKER_USERNAME }}" -p "${{ secrets.DOCKER_ACCESS_TOKEN }}" + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - name: Build docker api image - run: docker build -f docker/Dockerfile.api . -t $DOCKER_API_REPO:latest -t $DOCKER_API_REPO:$TAG_NAME + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Build docker ui image - run: docker build -f docker/Dockerfile.client . -t $DOCKER_UI_REPO:latest -t $DOCKER_UI_REPO:$TAG_NAME + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_ACCESS_TOKEN }} - - name: Push Docker api image - run: docker push $DOCKER_API_REPO:latest && docker push $DOCKER_API_REPO:$TAG_NAME + - name: Build and push API image + uses: docker/build-push-action@v5 + with: + context: . + file: docker/Dockerfile.api + platforms: linux/amd64,linux/arm64 + push: true + build-args: | + APP_ENV=${{ env.VERSION == 'dev' && 'local' || 'production' }} + tags: ${{ env.API_TAGS }} - - name: Push Docker ui image - run: docker push $DOCKER_UI_REPO:latest && docker push $DOCKER_UI_REPO:$TAG_NAME + - name: Build and push Client image + uses: docker/build-push-action@v5 + with: + context: . + file: docker/Dockerfile.client + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ env.UI_TAGS }} diff --git a/.gitignore b/.gitignore index c1996aca4..e4f011f39 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,14 @@ public/.DS_Store *.DS_Store docker-compose.override.yml /.make.* + +# Environment files +.env +api/.env +client/.env +.env.* +api/.env.* +client/.env.* +!.env.example +!api/.env.example +!client/.env.example diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 000000000..4068aed22 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,77 @@ +services: + api: &api-base + image: jhumanj/opnform-api:dev + build: + context: . + dockerfile: docker/Dockerfile.api + args: + APP_ENV: local + volumes: + - ./api:/usr/share/nginx/html:delegated + - /usr/share/nginx/html/vendor # Exclude vendor directory from the mount + - ./api/storage:/usr/share/nginx/html/storage:delegated # Mount storage directory directly + environment: + 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} + FILESYSTEM_DISK: local + LOCAL_FILESYSTEM_VISIBILITY: public + APP_ENV: local + PHP_IDE_CONFIG: "serverName=Docker" + XDEBUG_MODE: "${XDEBUG_MODE:-off}" + XDEBUG_CONFIG: "client_host=host.docker.internal" + APP_URL: "http://localhost" + depends_on: + db: + condition: service_healthy + + ui: + image: jhumanj/opnform-client:dev + build: + context: . + dockerfile: docker/Dockerfile.client + command: sh -c "npm install && NITRO_HOST=0.0.0.0 NITRO_PORT=3000 npm run dev" + volumes: + - ./client:/app:delegated + - /app/node_modules # Keep container's node_modules + environment: + NODE_ENV: development + NUXT_PUBLIC_APP_ENV: development + HOST: "0.0.0.0" + PORT: 3000 + # HMR settings + CHOKIDAR_USEPOLLING: "true" + WATCHPACK_POLLING: "true" + VITE_HMR_HOST: "localhost" + VITE_HMR_PORT: 24678 + # API settings + NUXT_PUBLIC_APP_URL_BASE: "http://localhost" + NUXT_PUBLIC_API_BASE: "http://localhost/api" + extra_hosts: + - "host.docker.internal:host-gateway" + ports: + - "3000:3000" # Main dev server + - "24678:24678" # Vite HMR port + + ingress: + volumes: + - ./docker/nginx.dev.conf:/etc/nginx/templates/default.conf.template + environment: + NGINX_HOST: localhost + NGINX_PORT: 80 + ports: + - "80:80" + depends_on: + - api + - ui + + api-worker: + <<: *api-base + environment: + IS_API_WORKER: "true" + depends_on: + db: + condition: service_healthy \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 6d028617a..3e1f8f673 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,8 @@ services: build: context: . dockerfile: docker/Dockerfile.api + args: + APP_ENV: production environment: &api-environment # Add this anchor DB_HOST: db REDIS_HOST: redis @@ -24,6 +26,8 @@ services: build: context: . dockerfile: docker/Dockerfile.api + args: + APP_ENV: production command: php artisan queue:work environment: <<: *api-environment @@ -50,6 +54,11 @@ services: POSTGRES_DB: ${DB_DATABASE:-forge} POSTGRES_USER: ${DB_USERNAME:-forge} POSTGRES_PASSWORD: ${DB_PASSWORD:-forge} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-forge}"] + interval: 5s + timeout: 5s + retries: 5 volumes: - postgres-data:/var/lib/postgresql/data diff --git a/docker/Dockerfile.api b/docker/Dockerfile.api index be07de840..c50c81c86 100644 --- a/docker/Dockerfile.api +++ b/docker/Dockerfile.api @@ -1,28 +1,50 @@ -FROM php:8.3-fpm +# Stage 1: Composer dependencies +FROM composer:latest as composer +WORKDIR /app +COPY api/composer.* ./ + +ARG APP_ENV=production +RUN if [ "$APP_ENV" = "production" ]; then \ + composer install --ignore-platform-req=php --no-dev --optimize-autoloader; \ + else \ + composer install --ignore-platform-req=php --optimize-autoloader; \ + fi -# syntax=docker/dockerfile:1.3-labs +# Stage 2: Final image +FROM php:8.3-fpm +# Install system dependencies and PHP extensions RUN apt-get update && apt-get install -y \ - libzip-dev \ + git \ + curl \ libpng-dev \ - postgresql-client \ + libonig-dev \ + libxml2-dev \ + zip \ + unzip \ libpq-dev \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer -ENV COMPOSER_ALLOW_SUPERUSER=1 -RUN docker-php-ext-install pdo pgsql pdo_pgsql gd bcmath zip \ + && docker-php-ext-install pdo_pgsql mbstring exif pcntl bcmath gd \ && pecl install redis \ && docker-php-ext-enable redis -WORKDIR /usr/share/nginx/html/ +# Install xdebug if not in production +ARG APP_ENV=production +RUN if [ "$APP_ENV" != "production" ]; then \ + pecl install xdebug && \ + docker-php-ext-enable xdebug; \ + fi -# Combine multiple ADD commands into one -COPY api/composer.json api/composer.lock api/artisan ./ -COPY api/app ./app +# Configure PHP +COPY docker/php/php.ini /usr/local/etc/php/conf.d/app.ini +COPY docker/php/php-fpm.conf /usr/local/etc/php-fpm.d/www.conf + +WORKDIR /usr/share/nginx/html + +# Copy application files +COPY api/artisan artisan COPY api/bootstrap ./bootstrap COPY api/config ./config +COPY api/app ./app COPY api/database ./database COPY api/public ./public COPY api/routes ./routes @@ -30,15 +52,13 @@ COPY api/tests ./tests COPY api/resources ./resources COPY api/storage ./storage -RUN sed 's_@php artisan package:discover_/bin/true_;' -i composer.json \ - && composer install --ignore-platform-req=php --no-dev --optimize-autoloader \ - && composer clear-cache \ - && php artisan package:discover --ansi \ - && chmod -R 775 storage \ - && chown -R www-data:www-data storage \ - && mkdir -p storage/framework/sessions storage/framework/views storage/framework/cache \ - && chown -R www-data:www-data storage \ - && chmod -R 775 storage +# Copy vendor directory from composer stage +COPY --from=composer /app/vendor ./vendor + +# Set permissions +RUN chmod -R 775 storage \ + && chmod -R 775 bootstrap/cache \ + && chown -R www-data:www-data /usr/share/nginx/html COPY docker/php-fpm-entrypoint /usr/local/bin/opnform-entrypoint RUN chmod a+x /usr/local/bin/* diff --git a/docker/nginx.conf b/docker/nginx.conf index 746ee8e88..d0c6b4259 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -4,38 +4,36 @@ map $original_uri $api_uri { } server { - listen 80; - server_name opnform; - root /app/public; + listen 80; + server_name opnform; + root /usr/share/nginx/html/public; - access_log /dev/stdout; - error_log /dev/stderr error; + access_log /dev/stdout; + error_log /dev/stderr error; - index index.html index.htm index.php; + index index.html index.htm index.php; - location / { - proxy_http_version 1.1; - proxy_pass http://ui:3000; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "Upgrade"; - } + location / { + proxy_http_version 1.1; + proxy_pass http://ui:3000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } - location ~/(api|open|local\/temp|forms\/assets)/ { - set $original_uri $uri; - try_files $uri $uri/ /index.php$is_args$args; - } + location ~/(api|open|local\/temp|forms\/assets)/ { + try_files $uri $uri/ /index.php?$query_string; + } - location ~ \.php$ { - fastcgi_split_path_info ^(.+\.php)(/.+)$; - fastcgi_pass api:9000; - fastcgi_index index.php; - include fastcgi_params; - #fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/$fastcgi_script_name; - fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php; - fastcgi_param REQUEST_URI $api_uri; - } + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass api:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php; + fastcgi_param REQUEST_URI $api_uri; + } } diff --git a/docker/nginx.dev.conf b/docker/nginx.dev.conf new file mode 100644 index 000000000..fef640471 --- /dev/null +++ b/docker/nginx.dev.conf @@ -0,0 +1,73 @@ +map $original_uri $api_uri { + ~^/api(/.*$) $1; + default $original_uri; +} + +server { + listen 80; + server_name opnform; + root /usr/share/nginx/html/public; + + access_log /dev/stdout; + error_log /dev/stderr error; + + index index.html index.htm index.php; + + # Development CORS headers + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-XSRF-TOKEN' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; + + # Handle preflight requests + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-XSRF-TOKEN' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + + # Development proxy settings + location / { + proxy_http_version 1.1; + proxy_pass http://ui:3000; + 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_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400; + proxy_buffering off; + } + + # HMR websocket support + location /_nuxt { + proxy_pass http://ui:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } + + location ~/(api|open|local\/temp|forms\/assets)/ { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass api:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php; + fastcgi_param REQUEST_URI $api_uri; + } +} \ No newline at end of file diff --git a/docs/contributing/getting-started.mdx b/docs/contributing/getting-started.mdx index 0c4df6f2b..6fec74994 100644 --- a/docs/contributing/getting-started.mdx +++ b/docs/contributing/getting-started.mdx @@ -1,8 +1,25 @@ --- -title: "Getting Started with Contributing" -description: "Learn how to contribute to OpnForm" +title: Getting Started +description: Learn how to contribute to OpnForm --- +## Development Setup + +### Docker Setup (Recommended) + +The easiest way to get started with OpnForm development is to use our Docker-based development environment. It provides: +- Hot-reload for both frontend and backend +- All necessary services pre-configured +- Consistent development environment across all platforms + +Follow our [Docker Development Setup](/deployment/docker-development) guide to get started. + +### Manual Setup + +If you prefer not to use Docker or need a custom setup, you can follow our [Local Deployment](/deployment/local-deployment#manual-setup) guide for manual installation instructions. + +## Contributing Guidelines + Welcome to the OpnForm contributing guide! Here are some helpful links to get you started: diff --git a/docs/deployment/docker-development.mdx b/docs/deployment/docker-development.mdx new file mode 100644 index 000000000..95914abe7 --- /dev/null +++ b/docs/deployment/docker-development.mdx @@ -0,0 +1,139 @@ +--- +title: "Docker Development Setup" +description: "Set up OpnForm locally for development using Docker" +--- + +import CloudVersion from "/snippets/cloud-version.mdx"; + + + +## Overview + +OpnForm provides a Docker-based development environment that offers: +- Hot-reload for both frontend and backend +- Xdebug support for PHP debugging +- Automatic dependency management +- PostgreSQL database and Redis setup +- Nginx reverse proxy configuration + +This is the recommended way to get started with OpnForm development. + +## Prerequisites + +- Docker and Docker Compose installed on your machine +- Git installed +- Basic understanding of Docker concepts + +## Quick Start + +1. Clone the repository: + ```bash + git clone https://github.com/JhumanJ/OpnForm.git + cd OpnForm + ``` + +2. Create environment files: + ```bash + ./scripts/setup-env.sh + ``` + This will create the necessary `.env` files for both the API and client. See our [Environment Variables](/configuration/environment-variables) guide for configuration details. + +3. Start the development environment: + ```bash + docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d + ``` + +4. Access your development environment: + - Frontend: http://localhost:3000 + - API: http://localhost/api + +### Initial Login + +After starting the development environment, use these credentials to access the app: + +- Email: `admin@opnform.com` +- Password: `password` + +You will be prompted to change your email and password after your first login. + +Public registration is disabled in the self-hosted version. Use the admin account to invite additional users. + +## Development Features + +### Hot Reload + +The development setup includes hot reload capabilities: +- Frontend (Nuxt.js): Changes to files in the `client` directory trigger automatic rebuilds +- Backend (Laravel): Changes to files in the `api` directory are immediately reflected, except for queued jobs which require restarting the api-worker container (`docker compose -f docker-compose.yml -f docker-compose.dev.yml restart api-worker`) + +### File Structure + +The development setup mounts your local directories into the containers: +- `./api`: Mounted to the API container with vendor directory preserved +- `./client`: Mounted to the UI container with node_modules preserved +- Database and Redis data are persisted through Docker volumes + +### Container Services + +The development environment includes: +- `api`: Laravel API service with hot reload +- `ui`: Nuxt.js frontend with HMR +- `api-worker`: Laravel queue worker +- `db`: PostgreSQL database +- `redis`: Redis server +- `ingress`: Nginx reverse proxy + +## Common Tasks + +### Running Commands + +To run commands in the containers: + +```bash +# Laravel Artisan commands +docker compose -f docker-compose.yml -f docker-compose.dev.yml exec api php artisan [command] + +# NPM commands +docker compose -f docker-compose.yml -f docker-compose.dev.yml exec ui npm [command] +``` + +### Accessing Logs + +View container logs: + +```bash +# All containers +docker compose -f docker-compose.yml -f docker-compose.dev.yml logs -f + +# Specific container +docker compose -f docker-compose.yml -f docker-compose.dev.yml logs -f [service] +``` + +### Database Management + +The PostgreSQL database is accessible: +- From containers: `host=db` +- From your machine: `localhost:5432` +- Default credentials: username=forge, password=forge, database=forge + +## Troubleshooting + +### Container Issues +If containers aren't starting properly: +```bash +# Remove all containers and volumes +docker compose down -v + +# Rebuild and start +docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build +``` + +### Permission Issues +If you encounter permission issues with storage or vendor directories: +```bash +# Fix storage permissions +docker compose -f docker-compose.yml -f docker-compose.dev.yml exec api chmod -R 775 storage + +# Fix vendor permissions +docker compose -f docker-compose.yml -f docker-compose.dev.yml exec api chmod -R 775 vendor +``` \ No newline at end of file diff --git a/docs/deployment/docker.mdx b/docs/deployment/docker.mdx index d90de9cd8..abec1a883 100644 --- a/docs/deployment/docker.mdx +++ b/docs/deployment/docker.mdx @@ -1,12 +1,16 @@ --- -title: "Docker" -description: "OpnForm can be easily set up using Docker. We provide pre-built images on Docker Hub, which is the recommended method for most users." +title: "Docker Deployment" +description: "Deploy OpnForm using Docker" --- import CloudVersion from "/snippets/cloud-version.mdx"; + +This guide is for deploying OpnForm on a production server. If you're looking to **develop OpnForm locally**, check out our [Docker Development Setup](/deployment/docker-development) guide which provides **hot-reload and other development features**. + + ## Prerequisites - Docker diff --git a/docs/deployment/local-deployment.mdx b/docs/deployment/local-deployment.mdx index c7154d24f..fe026bc4c 100644 --- a/docs/deployment/local-deployment.mdx +++ b/docs/deployment/local-deployment.mdx @@ -7,6 +7,16 @@ import CloudVersion from "/snippets/cloud-version.mdx"; +## Docker Development Setup (Recommended) + +We recommend using our Docker-based development environment for the easiest setup experience. It provides hot-reload, debugging support, and all necessary services pre-configured. + +See our [Docker Development Setup](/deployment/docker-development) guide to get started with Docker. + +## Manual Setup + +If you prefer to set up OpnForm manually or can't use Docker, follow the instructions below. + ## Requirements Before proceeding with the local deployment, ensure you have the following prerequisites installed on your system: diff --git a/docs/mint.json b/docs/mint.json index bf78aa3b5..028996f5b 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -73,6 +73,7 @@ "group": "Deployment", "pages": [ "deployment/docker", + "deployment/docker-development", "deployment/local-deployment", "deployment/cloud-vs-self-hosting" ]