Refactor Docker configuration and development setup

- Update .dockerignore with comprehensive ignore patterns for API and client
- Modify docker-compose files to improve service configurations
- Enhance Nginx configuration for development and production environments
- Refactor Dockerfile.api with improved build process
- Add docker-setup.sh script for simplified Docker deployment
- Update update-credentials.vue page with improved UI
- Remove hCaptcha dependency from package-lock.json
- Update PHP configuration and entrypoint scripts
This commit is contained in:
Julien Nahum 2025-01-29 16:00:01 +01:00
parent bf85d8fa76
commit f7df6bc0d7
26 changed files with 1978 additions and 1749 deletions

View File

@ -2,3 +2,38 @@
/Dockerfile
/data
\.env
# API ignores
api/vendor/
api/storage/framework/
api/storage/logs/
api/storage/app/
api/.env
api/.env.*
api/.git/
api/.idea/
api/.vscode/
api/node_modules/
api/*.log
# Client ignores
client/node_modules/
client/.nuxt/
client/dist/
client/.output/
client/coverage/
client/.env
client/.env.*
client/.git/
client/.idea/
client/.vscode/
client/npm-debug.log*
client/yarn-debug.log*
client/yarn-error.log*
# Global ignores
**/.DS_Store
**/*.log
**/.git/
**/.idea/
**/.vscode/

View File

@ -11,6 +11,7 @@ on:
- "client/**"
- "docker/**"
- "docker-compose*.yml"
- ".github/workflows/dockerhub.yml"
workflow_dispatch:
permissions:
@ -30,8 +31,8 @@ jobs:
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
echo "API_TAGS=${{secrets.DOCKER_API_REPO}}:dev,${{secrets.DOCKER_API_REPO}}:dev-${GITHUB_SHA::7}" >> $GITHUB_ENV
echo "UI_TAGS=${{secrets.DOCKER_UI_REPO}}:dev,${{secrets.DOCKER_UI_REPO}}:dev-${GITHUB_SHA::7}" >> $GITHUB_ENV
fi
- name: Check out the repo
@ -59,6 +60,8 @@ jobs:
build-args: |
APP_ENV=${{ env.VERSION == 'dev' && 'local' || 'production' }}
tags: ${{ env.API_TAGS }}
cache-from: type=registry,ref=${{secrets.DOCKER_API_REPO}}:dev
cache-to: type=inline
- name: Build and push Client image
uses: docker/build-push-action@v5
@ -68,3 +71,5 @@ jobs:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.UI_TAGS }}
cache-from: type=registry,ref=${{secrets.DOCKER_UI_REPO}}:dev
cache-to: type=inline

View File

@ -5,6 +5,7 @@ namespace App\Http;
use App\Http\Middleware\AcceptsJsonMiddleware;
use App\Http\Middleware\AuthenticateJWT;
use App\Http\Middleware\CustomDomainRestriction;
use App\Http\Middleware\DevCorsMiddleware;
use App\Http\Middleware\ImpersonationMiddleware;
use App\Http\Middleware\IsAdmin;
use App\Http\Middleware\IsModerator;
@ -25,6 +26,7 @@ class Kernel extends HttpKernel
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
DevCorsMiddleware::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
@ -66,7 +68,7 @@ class Kernel extends HttpKernel
],
'api-external' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class DevCorsMiddleware
{
public function handle(Request $request, Closure $next)
{
// Only apply in development mode
if (!config('app.dev_cors')) {
return $next($request);
}
$response = $next($request);
// Handle preflight OPTIONS request
if ($request->isMethod('OPTIONS')) {
$response = response('', 200);
}
// Add CORS headers
$response->headers->set('Access-Control-Allow-Origin', 'http://localhost:3000', true);
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, PATCH', true);
$response->headers->set('Access-Control-Allow-Headers', 'DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range, Authorization, X-XSRF-TOKEN, Accept', true);
$response->headers->set('Access-Control-Allow-Credentials', 'true', true);
$response->headers->set('Access-Control-Expose-Headers', 'Content-Length, Content-Range', true);
return $response;
}
}

0
api/bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

2723
api/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,8 @@ return [
'env' => env('APP_ENV', 'production'),
'dev_cors' => env('APP_DEV_CORS', false),
/*
|--------------------------------------------------------------------------
| Application Debug Mode

View File

@ -1,2 +0,0 @@
/Dockerfile
/.dockerignore

View File

@ -30,7 +30,7 @@
/>
<!-- Remember Me -->
<div class="relative flex items-start mt-5">
<div class="relative flex items-center mt-3">
<CheckboxInput
v-model="remember"
class="w-full md:w-1/2"
@ -52,7 +52,7 @@
<!-- Submit Button -->
<v-button
class="w-full flex"
class="w-full flex mt-2"
:loading="form.busy || loading"
>
Log in to continue

View File

@ -3,7 +3,8 @@ import { useFeatureFlagsStore } from '~/stores/featureFlags'
export default defineNuxtRouteMiddleware(async () => {
const featureFlagsStore = useFeatureFlagsStore()
if (import.meta.server && Object.keys(featureFlagsStore.flags).length === 0) {
// Load flags if they haven't been loaded yet
if (!featureFlagsStore.isLoaded) {
await featureFlagsStore.fetchFlags()
}
})

View File

@ -1,5 +1,11 @@
export default defineNuxtRouteMiddleware(() => {
export default defineNuxtRouteMiddleware(async () => {
const authStore = useAuthStore()
const featureFlagsStore = useFeatureFlagsStore()
// Ensure feature flags are loaded
if (!featureFlagsStore.isLoaded) {
await featureFlagsStore.fetchFlags()
}
if (useFeatureFlag('self_hosted')) {
if (authStore.check && authStore.user?.email === 'admin@opnform.com') {

View File

@ -8,7 +8,6 @@
"hasInstallScript": true,
"dependencies": {
"@codemirror/lang-html": "^6.4.9",
"@hcaptcha/vue3-hcaptcha": "^1.3.0",
"@iconify-json/material-symbols": "^1.2.4",
"@nuxt/ui": "^2.19.2",
"@pinia/nuxt": "^0.5.5",
@ -1338,18 +1337,6 @@
}
}
},
"node_modules/@hcaptcha/vue3-hcaptcha": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@hcaptcha/vue3-hcaptcha/-/vue3-hcaptcha-1.3.0.tgz",
"integrity": "sha512-IEonS6JiYdU7uy6aeib8cYtMO4nj8utwStbA9bWHyYbOvOvhpkV+AW8vfSKh6SntYxqle/TRwhv+kU9p92CfsA==",
"license": "MIT",
"dependencies": {
"vue": "^3.2.19"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/@headlessui/tailwindcss": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.1.tgz",

View File

@ -1,5 +1,5 @@
<template>
<div class="bg-white">
<div class="bg-white" v-if="workspace">
<div class="flex bg-gray-50 pb-5 border-b">
<div class="w-full md:w-4/5 lg:w-3/5 md:mx-auto md:max-w-4xl p-4">
<div class="pt-4 pb-0">
@ -8,7 +8,7 @@
Your Forms
</h2>
<v-button
v-if="!workspace.is_readonly"
v-if="!workspace?.is_readonly"
v-track.create_form_click
:to="{ name: 'forms-create' }"
>
@ -87,7 +87,7 @@
again.
</div>
<v-button
v-if="!workspace.is_readonly && forms.length === 0"
v-if="!workspace?.is_readonly && forms.length === 0"
v-track.create_form_click
class="mt-4"
:to="{ name: 'forms-create' }"
@ -182,7 +182,7 @@
</div>
</div>
<div
v-if="!workspace.is_pro"
v-if="!workspace?.is_pro"
class="px-4"
>
<UAlert

View File

@ -1,62 +1,74 @@
<template>
<modal
:show="showModal"
max-width="lg"
@close="logout"
>
<div class="">
<h2 class="font-medium text-3xl mb-3">
<div class=" bg-gray-50 flex flex-col justify-center sm:px-6 lg:px-8 py-10 flex-grow">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h2 class="text-center text-3xl font-bold tracking-tight text-gray-900">
Welcome to OpnForm!
</h2>
<p class="text-sm text-gray-600">
<p class="mt-2 text-center text-sm text-gray-600">
You're using the self-hosted version of OpnForm and need to set up your account.
Please enter your email and create a password to continue.
</p>
</div>
<form
class="mt-4"
@submit.prevent="updateCredentials"
@keydown="form.onKeydown($event)"
>
<!-- Email -->
<text-input
name="email"
:form="form"
label="Email"
:required="true"
placeholder="Your email address"
/>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<form
@submit.prevent="updateCredentials"
@keydown="form.onKeydown($event)"
>
<!-- Email -->
<text-input
name="email"
:form="form"
label="Email"
:required="true"
placeholder="Your email address"
/>
<!-- Password -->
<text-input
native-type="password"
placeholder="Your password"
name="password"
:form="form"
label="Password"
:required="true"
/>
<!-- Password -->
<text-input
native-type="password"
placeholder="Your password"
name="password"
:form="form"
label="Password"
:required="true"
/>
<!-- Password Confirmation-->
<text-input
native-type="password"
:form="form"
:required="true"
placeholder="Enter confirm password"
name="password_confirmation"
label="Confirm Password"
/>
<!-- Password Confirmation-->
<text-input
native-type="password"
:form="form"
:required="true"
placeholder="Enter confirm password"
name="password_confirmation"
label="Confirm Password"
/>
<!-- Submit Button -->
<v-button
class="mx-auto"
:loading="form.busy || loading"
>
Update Credentials
</v-button>
</form>
</modal>
<!-- Submit Button -->
<div class="mt-6">
<v-button
class="w-full justify-center"
:loading="form.busy || loading"
>
Update Credentials
</v-button>
</div>
<!-- Cancel Link -->
<div class="mt-4 text-center">
<button
type="button"
class="text-sm text-gray-600 hover:text-gray-900"
@click="logout"
>
Cancel and return to login
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup>
@ -67,7 +79,7 @@ const workspacesStore = useWorkspacesStore()
const formsStore = useFormsStore()
const user = computed(() => authStore.user)
const router = useRouter()
const showModal = ref(true)
const loading = ref(false)
const form = useForm({
name: "",
email: "",
@ -82,6 +94,7 @@ onMounted(() => {
})
const updateCredentials = () => {
loading.value = true
form
.post("update-credentials")
.then(async (data) => {
@ -95,11 +108,13 @@ const updateCredentials = () => {
console.error(error)
useAlert().error(error.response._data.message)
})
.finally(() => {
loading.value = false
})
}
const logout = () => {
authStore.logout()
showModal.value = false
router.push({ name: "login" })
}
</script>

View File

@ -3,11 +3,15 @@ import { ref } from 'vue'
export const useFeatureFlagsStore = defineStore('feature_flags', () => {
const flags = ref({})
const isLoaded = ref(false)
async function fetchFlags() {
if (isLoaded.value) return
try {
const { data } = await useOpnApi('content/feature-flags')
flags.value = data.value
isLoaded.value = true
} catch (error) {
console.error('Failed to fetch feature flags:', error)
}
@ -20,5 +24,5 @@ export const useFeatureFlagsStore = defineStore('feature_flags', () => {
}, flags.value)
}
return { flags, fetchFlags, getFlag }
return { flags, isLoaded, fetchFlags, getFlag }
})

View File

@ -1,35 +1,70 @@
services:
api: &api-base
image: jhumanj/opnform-api:dev
container_name: opnform-api
build:
context: .
dockerfile: docker/Dockerfile.api
args:
APP_ENV: local
volumes:
- APP_ENV=local
volumes: &api-base-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:
- ./api/storage:/usr/share/nginx/html/storage:delegated
environment: &api-base-env
APP_ENV: local
APP_DEV_CORS: "true"
IS_API_WORKER: "false"
# 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}
# Storage settings
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"
# Development settings
PHP_IDE_CONFIG: serverName=Docker
XDEBUG_MODE: ${XDEBUG_MODE:-off}
XDEBUG_CONFIG: client_host=host.docker.internal
APP_URL: http://localhost
FRONT_URL: http://localhost:3000
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
api-worker:
<<: *api-base
container_name: opnform-api-worker
command: ["php", "artisan", "queue:work"]
volumes: *api-base-volumes
environment:
<<: *api-base-env
APP_ENV: local
IS_API_WORKER: "true"
api-scheduler:
<<: *api-base
container_name: opnform-api-scheduler
command: ["php", "artisan", "schedule:work"]
volumes: *api-base-volumes
environment:
<<: *api-base-env
APP_ENV: local
IS_API_WORKER: "true"
AUTORUN_ENABLED: "false"
# Scheduler settings
CONTAINER_ROLE: scheduler
PHP_MEMORY_LIMIT: 512M
PHP_MAX_EXECUTION_TIME: 60
ui:
image: jhumanj/opnform-client:dev
container_name: opnform-client
build:
context: .
dockerfile: docker/Dockerfile.client
@ -57,6 +92,8 @@ services:
- "24678:24678" # Vite HMR port
ingress:
image: nginx:1
container_name: opnform-ingress
volumes:
- ./docker/nginx.dev.conf:/etc/nginx/templates/default.conf.template
environment:
@ -65,13 +102,7 @@ services:
ports:
- "80:80"
depends_on:
- api
- ui
api-worker:
<<: *api-base
environment:
IS_API_WORKER: "true"
depends_on:
db:
condition: service_healthy
api:
condition: service_started
ui:
condition: service_started

View File

@ -1,55 +1,81 @@
---
services:
api: &api
api: &api-environment
image: jhumanj/opnform-api:latest
container_name: opnform-api
build:
context: .
dockerfile: docker/Dockerfile.api
args:
APP_ENV: production
environment: &api-environment # Add this anchor
- APP_ENV=production
volumes: &api-environment-volumes
- ./api/storage:/usr/share/nginx/html/storage:rw
- ./api/bootstrap/cache:/usr/share/nginx/html/bootstrap/cache:rw
environment: &api-env
APP_ENV: production
IS_API_WORKER: "false"
# 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}
# Storage settings
FILESYSTEM_DISK: local
LOCAL_FILESYSTEM_VISIBILITY: public
env_file:
- ./api/.env
volumes:
- opnform_storage:/usr/share/nginx/html/storage:rw
- ./api/.env
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
api-worker:
image: jhumanj/opnform-api:latest
build:
context: .
dockerfile: docker/Dockerfile.api
args:
APP_ENV: production
command: php artisan queue:work
<<: *api-environment
container_name: opnform-api-worker
command: ["php", "artisan", "queue:work"]
volumes: *api-environment-volumes
environment:
<<: *api-environment
<<: *api-env
APP_ENV: production
IS_API_WORKER: "true"
env_file:
- ./api/.env
volumes:
- opnform_storage:/usr/share/nginx/html/storage:rw
- ./api/.env
api-scheduler:
<<: *api-environment
container_name: opnform-api-scheduler
command: ["php", "artisan", "schedule:work"]
volumes: *api-environment-volumes
environment:
<<: *api-env
APP_ENV: production
IS_API_WORKER: "true"
# Scheduler settings
CONTAINER_ROLE: scheduler
PHP_MEMORY_LIMIT: 512M
PHP_MAX_EXECUTION_TIME: 60
env_file:
- ./api/.env
ui:
image: jhumanj/opnform-client:latest
container_name: opnform-client
build:
context: .
dockerfile: docker/Dockerfile.client
env_file:
- ./client/.env
- ./client/.env
redis:
image: redis:7
container_name: opnform-redis
db:
image: postgres:16
container_name: opnform-db
environment:
POSTGRES_DB: ${DB_DATABASE:-forge}
POSTGRES_USER: ${DB_USERNAME:-forge}
@ -64,10 +90,16 @@ services:
ingress:
image: nginx:1
container_name: opnform-ingress
volumes:
- ./docker/nginx.conf:/etc/nginx/templates/default.conf.template
ports:
- 80:80
depends_on:
api:
condition: service_started
ui:
condition: service_started
volumes:
postgres-data:

View File

@ -1,65 +1,58 @@
# 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
# Stage 2: Final image
FROM php:8.3-fpm
# Install system dependencies and PHP extensions
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libzip-dev \
libpng-dev \
libonig-dev \
libxml2-dev \
zip \
unzip \
postgresql-client \
libpq-dev \
&& docker-php-ext-install pdo_pgsql mbstring exif pcntl bcmath gd \
unzip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
ENV COMPOSER_ALLOW_SUPERUSER=1
# Install PHP extensions
RUN docker-php-ext-install pdo pgsql pdo_pgsql gd bcmath zip \
&& pecl install redis \
&& docker-php-ext-enable redis
# Install xdebug if not in production
ARG APP_ENV=production
RUN if [ "$APP_ENV" != "production" ]; then \
pecl install xdebug && \
docker-php-ext-enable xdebug; \
WORKDIR /usr/share/nginx/html/
# Create storage directories
RUN mkdir -p storage/framework/sessions \
storage/framework/views \
storage/framework/cache \
storage/logs \
storage/app/public \
bootstrap/cache \
&& chown -R www-data:www-data storage bootstrap/cache \
&& chmod -R 775 storage bootstrap/cache
# Copy composer files and helpers.php first
COPY api/composer.json api/composer.lock ./
COPY api/app/helpers.php ./app/helpers.php
# Install dependencies without running scripts
RUN if [ "$APP_ENV" = "production" ] ; then \
composer install --no-dev --no-scripts --ignore-platform-req=php --optimize-autoloader ; \
else \
composer install --no-scripts --ignore-platform-req=php --optimize-autoloader ; \
fi
# 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
# Copy the rest of the application
COPY api/ .
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
COPY api/tests ./tests
COPY api/resources ./resources
COPY api/storage ./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
# Run composer scripts and clear cache
RUN composer dump-autoload -o \
&& php artisan package:discover --ansi \
&& composer clear-cache \
&& chmod -R 775 storage \
&& chown -R www-data:www-data storage
# Setup entrypoint
COPY docker/php-fpm-entrypoint /usr/local/bin/opnform-entrypoint
RUN chmod a+x /usr/local/bin/*

View File

@ -4,18 +4,18 @@ map $original_uri $api_uri {
}
server {
listen 80;
server_name opnform;
root /usr/share/nginx/html/public;
listen 80;
server_name opnform;
root /usr/share/nginx/html/public;
access_log /dev/stdout;
error_log /dev/stderr error;
error_log /dev/stderr error;
index index.html index.htm index.php;
location / {
proxy_http_version 1.1;
proxy_pass http://ui:3000;
proxy_pass http://opnform-client:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
@ -24,16 +24,22 @@ server {
}
location ~/(api|open|local\/temp|forms\/assets)/ {
try_files $uri $uri/ /index.php?$query_string;
set $original_uri $uri;
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass api:9000;
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 SCRIPT_FILENAME $document_root/index.php;
fastcgi_param REQUEST_URI $api_uri;
}
# Deny access to . files
location ~ /\. {
deny all;
}
}

View File

@ -4,35 +4,16 @@ map $original_uri $api_uri {
}
server {
listen 80;
server_name opnform;
root /usr/share/nginx/html/public;
listen 80;
server_name localhost;
root /usr/share/nginx/html/public;
access_log /dev/stdout;
error_log /dev/stderr error;
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
# Frontend proxy
location / {
proxy_http_version 1.1;
proxy_pass http://ui:3000;
@ -43,23 +24,23 @@ server {
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;
proxy_set_header Connection "Upgrade";
}
# HMR websocket support
location /_nuxt {
proxy_pass http://ui:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# API handling
location ~/(api|open|local\/temp|forms\/assets)/ {
try_files $uri $uri/ /index.php?$query_string;
set $original_uri $uri;
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
@ -67,7 +48,13 @@ server {
fastcgi_pass api:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html/public/index.php;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param REQUEST_URI $api_uri;
fastcgi_read_timeout 300;
}
# Deny access to . files
location ~ /\. {
deny all;
}
}

View File

@ -10,7 +10,7 @@ main() {
prep_storage
wait_for_db
apply_db_migrations
run_init_project
run_init_project
run_server "$@"
fi
}
@ -49,7 +49,7 @@ wait_for_db() {
apply_db_migrations() {
echo "Running DB Migrations"
./artisan migrate
./artisan migrate --force
}
run_init_project() {
@ -59,7 +59,7 @@ run_init_project() {
run_server() {
echo "Starting server $@"
exec /usr/local/bin/docker-php-entrypoint "$@"
exec "$@"
}
main "$@"

15
docker/php/php-fpm.conf Normal file
View File

@ -0,0 +1,15 @@
[www]
user = www-data
group = www-data
listen = 9000
pm = dynamic
pm.max_children = 20
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 1000
clear_env = no
catch_workers_output = yes
decorate_workers_output = no

27
docker/php/php.ini Normal file
View File

@ -0,0 +1,27 @@
; PHP Configuration
memory_limit = 512M
max_execution_time = 60
upload_max_filesize = 64M
post_max_size = 64M
max_input_vars = 3000
; Error reporting
error_reporting = E_ALL
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /dev/stderr
; Date
date.timezone = UTC
; Session
session.save_handler = redis
session.save_path = "tcp://redis:6379"
; OpCache
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=0

View File

@ -10,13 +10,15 @@ import CloudVersion from "/snippets/cloud-version.mdx";
## Overview
OpnForm provides a Docker-based development environment that offers:
- Hot-reload for both frontend and backend
- Hot Module Replacement (HMR) for real-time frontend updates
- Vue DevTools integration for debugging
- PHP hot reload for backend changes
- 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.
For details about the Docker architecture and components, see our [Docker Deployment](/deployment/docker) guide.
## Prerequisites
@ -32,18 +34,19 @@ This is the recommended way to get started with OpnForm development.
cd OpnForm
```
2. Create environment files:
2. Run the setup script in development mode:
```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
chmod +x scripts/docker-setup.sh
./scripts/docker-setup.sh --dev
```
4. Access your development environment:
This script will:
- Create necessary environment files
- Pull or build required Docker images
- Start all containers in development mode
- Display access information
3. Access your development environment:
- Frontend: http://localhost:3000
- API: http://localhost/api
@ -60,28 +63,54 @@ You will be prompted to change your email and password after your first login.
## Development Features
### Hot Reload
### Frontend Development
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`)
The development setup includes advanced frontend features:
- **Hot Module Replacement (HMR)**: Changes to Vue components and styles are instantly reflected without page reload
- **Vue DevTools**: Full integration for component inspection and state management debugging ([learn more](https://devtools.vuejs.org/))
- **Source Maps**: Enabled for easier debugging
- **Fast Refresh**: Preserves component state during updates
- **Error Overlay**: Displays errors directly in the browser
### File Structure
### Backend Development
The Laravel API service provides:
- **PHP Hot Reload**: Changes to PHP files are immediately available
- **Xdebug Integration**: Ready for step-by-step debugging
- **Laravel Telescope**: Available for request and queue monitoring
- **Artisan Commands**: Direct access to Laravel's CLI tools
<Note>
Queue workers require restart after code changes:
```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml restart api-worker
```
</Note>
### Development URLs
- **Frontend**: http://localhost:3000
- Direct access to Nuxt dev server
- Includes HMR websocket connection
- Vue DevTools available
- **API**: http://localhost/api
- Handled by Nginx reverse proxy
- Automatic routing to PHP-FPM
- Supports file uploads and long requests
## 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
```
OpnForm/
├── api/ # Laravel API (mounted to api container)
│ ├── vendor/ # Preserved in container
│ └── storage/ # Mounted for logs and uploads
├── client/ # Nuxt frontend (mounted to ui container)
│ └── node_modules/ # Preserved in container
└── docker/ # Docker configuration files
```
## Common Tasks
@ -95,6 +124,9 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml exec api php arti
# NPM commands
docker compose -f docker-compose.yml -f docker-compose.dev.yml exec ui npm [command]
# Database commands
docker compose -f docker-compose.yml -f docker-compose.dev.yml exec db psql -U forge
```
### Accessing Logs
@ -105,35 +137,48 @@ View container logs:
# 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]
# Specific container (e.g., frontend)
docker compose -f docker-compose.yml -f docker-compose.dev.yml logs -f ui
```
### Database Management
### Database Access
The PostgreSQL database is accessible:
- From containers: `host=db`
- From your machine: `localhost:5432`
- Default credentials: username=forge, password=forge, database=forge
- Default credentials:
```
Host: localhost
Port: 5432
Database: forge
Username: forge
Password: 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
# Clean everything and restart
./scripts/docker-setup.sh --dev
```
### Permission Issues
If you encounter permission issues with storage or vendor directories:
If you encounter permission issues:
```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
```
```
### HMR Issues
If hot reload isn't working:
1. Check browser console for WebSocket errors
2. Ensure ports 3000 and 24678 are available
3. Try restarting the UI container:
```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml restart ui
```

View File

@ -8,49 +8,34 @@ import CloudVersion from "/snippets/cloud-version.mdx";
<CloudVersion/>
<Tip>
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**.
Looking to develop OpnForm locally? Check out our [Docker Development Setup](/deployment/docker-development) guide which provides hot-reload and other development features.
</Tip>
## Prerequisites
- Docker
- Docker Compose
## Quick Start
1. Clone the repository:
```bash
git clone https://github.com/JhumanJ/OpnForm.git
cd OpnForm
```
2. Set up environment files:
2. Run the setup script:
```bash
./scripts/setup-env.sh --docker
chmod +x scripts/docker-setup.sh
./scripts/docker-setup.sh
```
3. (Optional) Customize the environment variables:
You can modify two environment files to customize your OpnForm installation:
- The `.env` file in the `api` directory for backend configuration
- The `.env` file in the `client` directory for frontend configuration
For more information on available environment variables, please refer to our [Environment Variables](/configuration/environment-variables) page.
4. Start the application:
```bash
docker compose up -d
```
5. Access OpnForm at http://localhost
The script will:
- Create necessary environment files
- Pull required Docker images
- Start all containers in production mode
- Display access information
3. Access your OpnForm instance at `http://localhost`
### Initial Login
After installation, use these credentials to access the app:
After deployment, use these credentials to access the app:
- Email: `admin@opnform.com`
- Password: `password`
@ -59,71 +44,171 @@ You will be prompted to change your email and password after your first login.
<Note>Public registration is disabled in the self-hosted version. Use the admin account to invite additional users.</Note>
## Architecture
```mermaid
graph TD
A[Nginx Proxy] --> B[Frontend - Nuxt SSR]
A --> C[Backend - Laravel API]
C --> D[PostgreSQL]
C --> E[Redis]
C --> F[Queue Worker]
C --> G[Scheduler]
```
### Components
<Tabs>
<Tab title="Frontend">
The Nuxt frontend service:
- Server-Side Rendered application
- Built with Vue 3 and Tailwind CSS
- Handles dynamic rendering and client-side interactivity
- Optimized for production performance
</Tab>
<Tab title="Backend">
The Laravel API service:
- Handles business logic and data persistence
- Provides REST API endpoints
- Manages file uploads and processing
- Includes required PHP extensions (pgsql, redis, etc.)
- Configured for PostgreSQL and Redis connections
</Tab>
<Tab title="Workers">
Background processing services:
- **API Worker**: Processes queued jobs (emails, exports, etc.)
- **API Scheduler**: Handles scheduled tasks and periodic cleanups
- Both share the same codebase as the main API
</Tab>
<Tab title="Databases">
Data storage services:
- **PostgreSQL**: Primary database for all application data
- **Redis**: Used for:
- Session storage
- Cache
- Queue management
- Real-time features
</Tab>
<Tab title="Proxy">
The Nginx proxy service:
- Routes requests between frontend and backend
- Handles SSL termination
- Manages file upload limits
- Serves static assets
- Configured for optimal performance
</Tab>
</Tabs>
## Docker Images
OpnForm provides pre-built Docker images for easy deployment. You can find our official Docker images on Docker Hub:
OpnForm provides pre-built Docker images for easy deployment:
- [OpnForm API Image](https://hub.docker.com/r/jhumanj/opnform-api)
- [OpnForm Client Image](https://hub.docker.com/r/jhumanj/opnform-client)
We recommend using these official images for your OpnForm deployment.
### Building Custom Images
## Building Your Own Docker Images
By default, OpnForm uses pre-built images from Docker Hub. However, if you want to build the images yourself (for development or customization), you have two options:
### Option 1: Using Docker Compose (Recommended)
The simplest way to build the images is using Docker Compose:
While we recommend using the official images, you can build custom images if needed:
```bash
# Build all images
docker compose build
# Or build specific images
docker build -t opnform-api:local -f docker/Dockerfile.api .
docker build -t opnform-ui:local -f docker/Dockerfile.client .
```
This will build all the necessary images. You can then start the application as usual:
```bash
docker compose up -d
```
### Option 2: Building Images Manually
You can also build the images manually using the provided Dockerfiles:
1. Build the API image:
```bash
docker build -t opnform-api:local -f docker/Dockerfile.api .
```
2. Build the UI image:
```bash
docker build -t opnform-ui:local -f docker/Dockerfile.client .
```
If you build the images manually, make sure to update your docker-compose.override.yml to use these local images (see below).
### Overriding Docker Compose Configuration
You can override the default Docker Compose configuration by creating a `docker-compose.override.yml` file. This allows you to customize various aspects of the deployment without modifying the main `docker-compose.yml` file.
Example `docker-compose.override.yml`:
### Custom Configuration
Create a `docker-compose.override.yml` to customize your deployment:
```yaml
services:
api:
image: opnform-api:local
environment:
PHP_MEMORY_LIMIT: 1G
ui:
image: opnform-ui:local
api-worker:
image: opnform-api:local
ingress:
volumes:
- ./custom-nginx.conf:/etc/nginx/conf.d/default.conf
```
## Maintenance
### Clearing all resources
### Updates
To completely remove all Docker containers, networks, and volumes created by Docker Compose and also remove all images used by these services, you can use the following command:
1. Pull latest changes:
```bash
git pull origin main
```
2. Update containers:
```bash
docker compose pull
docker compose up -d
```
### Monitoring
View container logs:
```bash
# All containers
docker compose logs -f
# Specific container
docker compose logs -f api
```
docker compose down -v --rmi all
Monitor container health:
```bash
docker compose ps
```
## Troubleshooting
### Container Issues
If containers aren't starting:
```bash
# View detailed logs
docker compose logs -f
# Recreate containers
docker compose down
docker compose up -d
```
### Database Issues
If database connections fail:
```bash
# Check database status
docker compose exec db pg_isready
# View database logs
docker compose logs db
```
### Cache Issues
Clear various caches:
```bash
# Clear application cache
docker compose exec api php artisan cache:clear
# Clear config cache
docker compose exec api php artisan config:clear
# Clear route cache
docker compose exec api php artisan route:clear
```
### Permission Issues
Fix storage permissions:
```bash
docker compose exec api chown -R www-data:www-data storage
docker compose exec api chmod -R 775 storage
```

69
scripts/docker-setup.sh Executable file
View File

@ -0,0 +1,69 @@
#!/bin/bash
set -e
# Colors for output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
# ASCII Art
echo -e "${BLUE}"
cat << "EOF"
____ ______
/ __ \____ ____ / ____/___ _________ ___
/ / / / __ \/ __ \/ /_ / __ \/ ___/ __ `__ \
/ /_/ / /_/ / / / / __/ / /_/ / / / / / / / /
\____/ .___/_/ /_/_/ \____/_/ /_/ /_/ /_/
/_/
EOF
echo -e "${NC}"
# Default values
DEV_MODE=false
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
# Parse command line arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--dev) DEV_MODE=true ;;
*) echo "Unknown parameter: $1"; exit 1 ;;
esac
shift
done
cd "$PROJECT_ROOT"
echo -e "${BLUE}Starting OpnForm Docker setup...${NC}"
# Run the environment setup script with --docker flag
echo -e "${GREEN}Setting up environment files...${NC}"
bash "$SCRIPT_DIR/setup-env.sh" --docker
# Determine which compose files to use
COMPOSE_FILES="-f docker-compose.yml"
if [ "$DEV_MODE" = true ]; then
echo -e "${YELLOW}Development mode enabled - using docker-compose.dev.yml${NC}"
COMPOSE_FILES="$COMPOSE_FILES -f docker-compose.dev.yml"
fi
# Start Docker containers
echo -e "${GREEN}Starting Docker containers...${NC}"
docker compose $COMPOSE_FILES up -d
# Display access instructions
if [ "$DEV_MODE" = true ]; then
echo -e "${BLUE}Development environment setup complete!${NC}"
echo -e "${YELLOW}Please wait for the frontend to finish building (this may take a few minutes)${NC}"
echo -e "${GREEN}Then visit: http://localhost:3000${NC}"
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}"
fi
echo -e "${BLUE}Default admin credentials:${NC}"
echo -e "${GREEN}Email: admin@opnform.com${NC}"
echo -e "${GREEN}Password: password${NC}"