Run development on the production stack with a Vite dev server
docker-compose.dev.yml extends docker-compose.yml, so development runs the scheduler too, and takes its settings from .env, which selects the file through COMPOSE_FILE. The dev image is a stage of the Dockerfile and shares the production image's PHP extensions; the app listens on port 80 as in production. A vite service runs the dev server with hot reload. No ports are published: OrbStack serves https://app.sealshare.orb.local and https://vite.sealshare.orb.local, with the ports pinned by label. Without OrbStack, docker-compose.ports.yml publishes APP_PORT and VITE_PORT on 127.0.0.1. vite.config.js takes the dev server's address from VITE_DEV_SERVER_URL and listens on IPv4 and IPv6, as OrbStack's proxy connects over either. Each start installs Composer packages, clears caches, migrates and links storage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9bb144577b
commit
026912f98a
+13
-1
@@ -72,5 +72,17 @@ VITE_APP_NAME="${APP_NAME}"
|
||||
# Uploads: each encrypted chunk the browser sends, in MB
|
||||
# UPLOAD_CHUNK_SIZE_MB=16
|
||||
|
||||
# Docker (used only when deploying with docker-compose.yml)
|
||||
# Docker development: `docker compose up` runs this file. On OrbStack, also set
|
||||
# APP_URL=https://app.sealshare.orb.local and VITE_DEV_SERVER_URL=https://vite.sealshare.orb.local.
|
||||
# Without OrbStack, append :docker-compose.ports.yml and set APP_URL=http://localhost:8000.
|
||||
COMPOSE_FILE=docker-compose.dev.yml
|
||||
|
||||
# Where the browser reaches the Vite dev server, when not on http://localhost
|
||||
# VITE_DEV_SERVER_URL=https://vite.sealshare.orb.local
|
||||
|
||||
# Ports on this machine: the Vite dev server's, and the app's with docker-compose.ports.yml
|
||||
# VITE_PORT=5173
|
||||
# APP_PORT=8000
|
||||
|
||||
# Docker production (docker compose -f docker-compose.yml), with AUTO_HTTPS=true
|
||||
# SERVER_NAME=share.example.com
|
||||
|
||||
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- A share that reached its download limit is closed at once, but deleted by the hourly cleanup 24 hours after its last download instead of immediately, so downloads still running can finish. Until then its files still count towards the storage quota.
|
||||
- The download page of a share with a download limit says how many downloads are left, or how long the recipient can still download. The admin dashboard shows downloads as "2 of 3 downloads", marks shares at their limit as "Download limit reached" and no longer counts them as active.
|
||||
- The sort dropdown on the admin dashboard spans the full width of the shares card.
|
||||
- Development: `docker-compose.dev.yml` now extends `docker-compose.yml`, so the dev stack runs the scheduler and the production image's PHP extensions, and takes its settings from `.env` (which selects the file through `COMPOSE_FILE`). A Vite dev server with hot reload runs beside the app. No ports are published unless `docker-compose.ports.yml` is added; with OrbStack the app is at `https://app.sealshare.orb.local`. `docker/dev.Dockerfile` became the `dev` stage of the `Dockerfile`.
|
||||
|
||||
## [2.1.0] - 2026-09-16
|
||||
|
||||
|
||||
+32
-10
@@ -39,20 +39,44 @@ COPY --from=vendor /app/vendor/nonameweb ./vendor/nonameweb
|
||||
RUN npm run build
|
||||
|
||||
# ============================================
|
||||
# Stage 3: Production image (FrankenPHP/Octane)
|
||||
# Stage 3: PHP runtime, shared by development and production
|
||||
# ============================================
|
||||
FROM dunglas/frankenphp:php8.5-alpine AS production
|
||||
FROM dunglas/frankenphp:php8.5-alpine AS base
|
||||
|
||||
LABEL maintainer="surtic86"
|
||||
LABEL org.opencontainers.image.source="https://gitea.nonameweb.ch/noNameWEB/SealShare"
|
||||
LABEL org.opencontainers.image.description="Self-hosted encrypted file sharing"
|
||||
|
||||
# Install required PHP extensions
|
||||
RUN install-php-extensions \
|
||||
intl \
|
||||
pcntl \
|
||||
zip
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ============================================
|
||||
# Stage 4: Development image (docker-compose.dev.yml)
|
||||
# ============================================
|
||||
# Holds only the tools: the checkout is mounted at /app, and its entrypoint runs from there.
|
||||
FROM base AS dev
|
||||
|
||||
# For the dev packages: Pest's browser plugin needs sockets
|
||||
RUN install-php-extensions sockets
|
||||
|
||||
# Node.js for the Vite dev server
|
||||
RUN apk add --no-cache nodejs npm
|
||||
|
||||
# Composer: the entrypoint installs the packages on every start
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
ENTRYPOINT ["docker/dev-entrypoint.sh"]
|
||||
|
||||
# ============================================
|
||||
# Stage 5: Production image (FrankenPHP/Octane)
|
||||
# ============================================
|
||||
# The last stage, so a build without --target builds this one.
|
||||
FROM base AS production
|
||||
|
||||
LABEL maintainer="surtic86"
|
||||
LABEL org.opencontainers.image.source="https://gitea.nonameweb.ch/noNameWEB/SealShare"
|
||||
LABEL org.opencontainers.image.description="Self-hosted encrypted file sharing"
|
||||
|
||||
# Laravel environment defaults
|
||||
ENV APP_NAME="SealShare" \
|
||||
APP_ENV="production" \
|
||||
@@ -69,8 +93,6 @@ ENV APP_NAME="SealShare" \
|
||||
BCRYPT_ROUNDS="12" \
|
||||
OCTANE_SERVER="frankenphp"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy PHP ini for upload limits
|
||||
COPY docker/php/uploads.ini /usr/local/etc/php/conf.d/99-uploads.ini
|
||||
|
||||
@@ -84,7 +106,7 @@ COPY --from=vendor /app/vendor ./vendor
|
||||
COPY --from=assets /app/public/build ./public/build
|
||||
|
||||
# Remove dev/build files and stale cache not needed in production
|
||||
RUN rm -rf node_modules tests .gitea docker/dev.Dockerfile docker/dev-entrypoint.sh .env .env.example \
|
||||
RUN rm -rf node_modules tests .gitea docker/dev-entrypoint.sh .env .env.example \
|
||||
bootstrap/cache/*.php \
|
||||
&& mkdir -p storage/app/shares storage/app/public storage/framework/cache \
|
||||
storage/framework/sessions storage/framework/testing storage/framework/views \
|
||||
|
||||
@@ -52,15 +52,35 @@ A simple, self-hosted file sharing solution built with Laravel. Upload files, ge
|
||||
|
||||
### Docker (recommended)
|
||||
|
||||
```bash
|
||||
# Build and start the dev container
|
||||
docker compose -f docker-compose.dev.yml up -d --build
|
||||
`docker-compose.dev.yml` extends the production stack (`docker-compose.yml`, app and scheduler): the checkout mounted at `/app`, Octane reloading on PHP changes, and a Vite dev server with HMR. `.env` selects it through `COMPOSE_FILE`, so plain `docker compose` commands work.
|
||||
|
||||
# View logs (including Vite output)
|
||||
docker compose -f docker-compose.dev.yml logs -f
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Set APP_KEY (composer setup generates one) and the values for your setup (below)
|
||||
|
||||
# Build and start the app, the scheduler and Vite
|
||||
docker compose up -d --build
|
||||
|
||||
# View logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
The app is available at `http://localhost:8000` with Vite HMR on port `5173`.
|
||||
With [OrbStack](https://orbstack.dev), no ports are published: set these in `.env` and open `https://app.sealshare.orb.local`. Uploads need HTTPS or `localhost`, because browsers only encrypt files there.
|
||||
|
||||
```dotenv
|
||||
COMPOSE_FILE=docker-compose.dev.yml
|
||||
APP_URL=https://app.sealshare.orb.local
|
||||
VITE_DEV_SERVER_URL=https://vite.sealshare.orb.local
|
||||
```
|
||||
|
||||
Without OrbStack, publish the ports on `127.0.0.1` and open `http://localhost:8000` (change the ports with `APP_PORT` and `VITE_PORT`):
|
||||
|
||||
```dotenv
|
||||
COMPOSE_FILE=docker-compose.dev.yml:docker-compose.ports.yml
|
||||
APP_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
The containers read `.env` when they are created: run `docker compose up -d` again after changing it.
|
||||
|
||||
|
||||
## Installation — Production
|
||||
|
||||
+68
-27
@@ -1,37 +1,78 @@
|
||||
# ============================================
|
||||
# SealShare - Development (extends docker-compose.yml)
|
||||
# ============================================
|
||||
#
|
||||
# The app and the scheduler extend the production services and change only what development needs.
|
||||
# Select this file in .env, then use plain `docker compose` commands:
|
||||
# COMPOSE_FILE=docker-compose.dev.yml
|
||||
#
|
||||
# The checkout is mounted at /app, so changes apply without a rebuild: Octane reloads on PHP changes,
|
||||
# the Vite dev server hot-reloads CSS and JavaScript and reloads the page on Blade changes. Every other
|
||||
# value comes from .env through docker-compose.yml.
|
||||
#
|
||||
# No ports are published: OrbStack serves https://app.sealshare.orb.local and
|
||||
# https://vite.sealshare.orb.local. Elsewhere, add docker-compose.ports.yml to COMPOSE_FILE.
|
||||
#
|
||||
# ============================================
|
||||
|
||||
# Only what differs between the host and the container: the host reads .env too.
|
||||
x-container-environment: &container-environment
|
||||
# Compiled views stay in the container. The host shares storage/ through the mount, and
|
||||
# compiled Livewire components hold absolute paths (/app/… here, the checkout's path there).
|
||||
VIEW_COMPILED_PATH: /tmp/views
|
||||
# The database file the host uses, not the production volume's path
|
||||
DB_DATABASE: /app/database/database.sqlite
|
||||
|
||||
services:
|
||||
app:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: app
|
||||
# Its own name, so a development build never tags the published image
|
||||
image: sealshare-dev
|
||||
build:
|
||||
target: dev
|
||||
ports: !reset []
|
||||
volumes: !override
|
||||
- .:/app
|
||||
environment: *container-environment
|
||||
labels:
|
||||
# OrbStack's port for https://app.sealshare.orb.local, instead of detecting it (it can keep a stale one)
|
||||
dev.orbstack.http-port: "80"
|
||||
healthcheck:
|
||||
# The first start installs Composer packages
|
||||
start_period: 5m
|
||||
start_interval: 2s
|
||||
|
||||
scheduler:
|
||||
extends:
|
||||
file: docker-compose.yml
|
||||
service: scheduler
|
||||
image: sealshare-dev
|
||||
build:
|
||||
target: dev
|
||||
volumes: !override
|
||||
- .:/app
|
||||
environment: *container-environment
|
||||
|
||||
vite:
|
||||
image: sealshare-dev
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/dev.Dockerfile
|
||||
ports:
|
||||
- "8000:8000"
|
||||
- "5173:5173"
|
||||
dockerfile: Dockerfile
|
||||
target: dev
|
||||
entrypoint: ["sh", "-c", "npm install --no-audit --no-fund && exec node_modules/.bin/vite"]
|
||||
volumes:
|
||||
- .:/app
|
||||
# Its own node_modules: npm installs the build tools' native binaries for Linux here and for
|
||||
# the host's platform there, and a shared folder only ever holds one of them.
|
||||
- /app/node_modules
|
||||
environment:
|
||||
APP_KEY: ${APP_KEY:-}
|
||||
APP_URL: http://localhost:8000
|
||||
APP_ENV: local
|
||||
# Compiled views stay in the container. The host shares storage/ through the mount, and
|
||||
# compiled Livewire components hold absolute paths (/app/… here, the checkout's path there).
|
||||
VIEW_COMPILED_PATH: /tmp/views
|
||||
APP_DEBUG: "true"
|
||||
SERVER_NAME: ":8000"
|
||||
DB_CONNECTION: sqlite
|
||||
LOG_CHANNEL: stack
|
||||
LOG_LEVEL: debug
|
||||
OCTANE_MAX_EXECUTION_TIME: "300"
|
||||
PHP_UPLOAD_MAX_FILESIZE: "64M"
|
||||
PHP_POST_MAX_SIZE: "64M"
|
||||
PHP_MAX_EXECUTION_TIME: "300"
|
||||
PHP_MAX_INPUT_TIME: "300"
|
||||
PHP_MEMORY_LIMIT: "512M"
|
||||
labels:
|
||||
dev.orbstack.http-port: "${VITE_PORT:-5173}"
|
||||
# The image's healthcheck asks the web server, which only the app service runs
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "--silent", "--fail", "http://localhost:8000/up"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
start_period: 30s
|
||||
retries: 3
|
||||
disable: true
|
||||
depends_on:
|
||||
# The stylesheet imports Livewire Material from vendor/, which the app's first start installs
|
||||
app:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# ============================================
|
||||
# SealShare - Development ports (layered on docker-compose.dev.yml)
|
||||
# ============================================
|
||||
#
|
||||
# Publishes the app and the Vite dev server on this machine, for Docker without OrbStack's domains:
|
||||
# COMPOSE_FILE=docker-compose.dev.yml:docker-compose.ports.yml
|
||||
# APP_URL=http://localhost:8000
|
||||
#
|
||||
# Bound to 127.0.0.1: a debug build does not belong on the network.
|
||||
#
|
||||
# ============================================
|
||||
|
||||
services:
|
||||
app:
|
||||
ports:
|
||||
- "127.0.0.1:${APP_PORT:-8000}:80"
|
||||
|
||||
vite:
|
||||
ports:
|
||||
- "127.0.0.1:${VITE_PORT:-5173}:${VITE_PORT:-5173}"
|
||||
@@ -13,16 +13,13 @@ max_input_time = ${PHP_MAX_INPUT_TIME:-300}
|
||||
memory_limit = ${PHP_MEMORY_LIMIT:-512M}
|
||||
EOF
|
||||
|
||||
if [ ! -f vendor/autoload.php ]; then
|
||||
echo "[dev] Installing PHP dependencies..."
|
||||
composer install --no-interaction 2>&1
|
||||
fi
|
||||
# Every start, so a pull with new packages needs no extra step; with nothing new it takes a second
|
||||
echo "[dev] Installing PHP dependencies..."
|
||||
composer install --no-interaction 2>&1
|
||||
|
||||
echo "[dev] Installing Node dependencies..."
|
||||
npm install 2>&1
|
||||
|
||||
echo "[dev] Building frontend assets..."
|
||||
npm run build 2>&1
|
||||
# A config or route cache left by `php artisan optimize` would hide changes to the checkout
|
||||
echo "[dev] Clearing caches..."
|
||||
php artisan optimize:clear
|
||||
|
||||
echo "[dev] Running database migrations..."
|
||||
php artisan migrate --force
|
||||
@@ -30,5 +27,6 @@ php artisan migrate --force
|
||||
echo "[dev] Creating storage link..."
|
||||
php artisan storage:link --force
|
||||
|
||||
# Port 80 as in production, so the same healthcheck applies. The vite service serves the assets.
|
||||
echo "[dev] Starting Octane (FrankenPHP) with --watch..."
|
||||
exec php artisan octane:frankenphp --host=0.0.0.0 --port=8000 --watch --workers=1 --max-requests=1
|
||||
exec php artisan octane:frankenphp --host=0.0.0.0 --port=80 --watch --workers=1 --max-requests=1
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
FROM dunglas/frankenphp:php8.5-alpine
|
||||
|
||||
# Install required PHP extensions
|
||||
RUN install-php-extensions \
|
||||
intl \
|
||||
pcntl
|
||||
|
||||
# Install Node.js for Vite / frontend asset building
|
||||
RUN apk add --no-cache nodejs npm
|
||||
|
||||
# Composer, for a checkout without vendor/: the assets import Livewire Material from it
|
||||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY docker/dev-entrypoint.sh /usr/local/bin/dev-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/dev-entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["dev-entrypoint.sh"]
|
||||
+35
-18
@@ -1,24 +1,41 @@
|
||||
import {
|
||||
defineConfig
|
||||
defineConfig,
|
||||
loadEnv
|
||||
} from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
||||
refresh: true,
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
cors: true,
|
||||
hmr: {
|
||||
host: 'localhost',
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
|
||||
// Where the browser reaches the dev server when it is not http://localhost, e.g. OrbStack's
|
||||
// https://vite.sealshare.orb.local. Laravel loads the assets from it, and HMR connects to it.
|
||||
const devServerUrl = env.VITE_DEV_SERVER_URL ? new URL(env.VITE_DEV_SERVER_URL) : null;
|
||||
const secure = devServerUrl?.protocol === 'https:';
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css', 'resources/js/app.js'],
|
||||
refresh: true,
|
||||
}),
|
||||
],
|
||||
server: {
|
||||
// IPv4 and IPv6: OrbStack's HTTPS proxy connects over either
|
||||
host: true,
|
||||
port: Number(env.VITE_PORT) || 5173,
|
||||
cors: true,
|
||||
origin: devServerUrl?.origin,
|
||||
allowedHosts: devServerUrl ? [devServerUrl.hostname] : [],
|
||||
hmr: devServerUrl ? {
|
||||
protocol: secure ? 'wss' : 'ws',
|
||||
host: devServerUrl.hostname,
|
||||
clientPort: Number(devServerUrl.port) || (secure ? 443 : 80),
|
||||
} : {
|
||||
host: 'localhost',
|
||||
},
|
||||
watch: {
|
||||
ignored: ['**/storage/framework/views/**'],
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
ignored: ['**/storage/framework/views/**'],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user