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,

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,21 +1,18 @@
<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>
<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
class="mt-4"
@submit.prevent="updateCredentials"
@keydown="form.onKeydown($event)"
>
@ -49,14 +46,29 @@
/>
<!-- Submit Button -->
<div class="mt-6">
<v-button
class="mx-auto"
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>
</modal>
</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,44 +1,68 @@
---
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
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
environment: &api-environment # Add this anchor
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
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
environment:
<<: *api-environment
container_name: opnform-api-worker
command: ["php", "artisan", "queue:work"]
volumes: *api-environment-volumes
environment:
<<: *api-env
APP_ENV: production
IS_API_WORKER: "true"
env_file:
- ./api/.env
volumes:
- opnform_storage:/usr/share/nginx/html/storage:rw
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
@ -47,9 +71,11 @@ services:
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

@ -15,7 +15,7 @@ server {
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

@ -5,7 +5,7 @@ map $original_uri $api_uri {
server {
listen 80;
server_name opnform;
server_name localhost;
root /usr/share/nginx/html/public;
access_log /dev/stdout;
@ -13,26 +13,7 @@ server {
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

@ -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,31 +137,35 @@ 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
@ -137,3 +173,12 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml exec api chmod -R
# 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
```
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
# Or build specific images
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
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:
### Updates
1. Pull latest changes:
```bash
git pull origin main
```
docker compose down -v --rmi all
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
```
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}"