1+ # Lychee - Laravel Backend Dockerfile
2+ # Multi-stage build with Laravel Octane + FrankenPHP
3+ ARG NODE_ENV=production
4+
5+ # ============================================================================
6+ # Stage 1: Composer Dependencies
7+ # ============================================================================
8+ FROM composer:2.8@sha256:5248900ab8b5f7f880c2d62180e40960cd87f60149ec9a1abfd62ac72a02577c AS composer
9+
10+ WORKDIR /app
11+
12+ # Copy composer files first for layer caching
13+ COPY composer.json composer.lock ./
14+
15+ # Install dependencies (no dev packages for production)
16+ RUN composer install \
17+ --no-dev \
18+ --no-interaction \
19+ --no-progress \
20+ --no-scripts \
21+ --prefer-dist \
22+ --optimize-autoloader \
23+ --ignore-platform-reqs
24+
25+ # ============================================================================
26+ # Stage 2: Node.js Build for Frontend Assets
27+ # ============================================================================
28+ FROM node:20-alpine@sha256:658d0f63e501824d6c23e06d4bb95c71e7d704537c9d9272f488ac03a370d448 AS node
29+
30+ # Build argument to control dev vs production build
31+ ARG NODE_ENV
32+ ENV NODE_ENV=$NODE_ENV
33+
34+ WORKDIR /app
35+
36+ # Copy package files for layer caching
37+ COPY package.json package-lock.json ./
38+
39+ # Install dependencies
40+ RUN npm ci --no-audit
41+
42+ # Copy frontend source
43+ COPY resources/ ./resources/
44+ COPY public/ ./public/
45+ COPY lang/ ./lang/
46+ COPY vite.config.ts vite.embed.config.ts tsconfig.json ./
47+
48+ # Build frontend assets
49+ # When NODE_ENV=development, Vite sets import.meta.env.DEV=true
50+ RUN npm run build
51+
52+ # ============================================================================
53+ # Stage 3: Production FrankenPHP Image
54+ # ============================================================================
55+ FROM dunglas/frankenphp:php8.4-alpine@sha256:49654aea8f2b9bc225bde6d89c9011054505ca2ed3e9874b251035128518b491
56+
57+ ARG USER=appuser
58+
59+ LABEL maintainer="lycheeorg"
60+ LABEL org.opencontainers.image.source="https://github.com/LycheeOrg/Lychee"
61+
62+ # Install system utilities and PHP extensions
63+ # hadolint ignore=DL3018
64+ RUN apk add --no-cache \
65+ exiftool \
66+ shadow \
67+ ffmpeg \
68+ gd \
69+ grep \
70+ imagemagick \
71+ jpegoptim \
72+ netcat-openbsd \
73+ unzip \
74+ curl \
75+ && install-php-extensions \
76+ pdo_mysql \
77+ pdo_pgsql \
78+ gd \
79+ zip \
80+ dom \
81+ bcmath \
82+ sodium \
83+ opcache \
84+ pcntl \
85+ exif \
86+ imagick \
87+ intl \
88+ redis \
89+ tokenizer \
90+ && rm -rf /var/cache/apk/* \
91+ && apk del shadow
92+
93+ WORKDIR /app
94+
95+ # Copy application code
96+ COPY --chown=www-data:www-data . .
97+
98+ # Copy vendor from composer stage
99+ COPY --from=composer --chown=www-data:www-data /app/vendor ./vendor
100+
101+ # Copy built frontend assets from node stage
102+ COPY --from=node --chown=www-data:www-data /app/public/build ./public/build
103+
104+ # Ensure storage and bootstrap/cache are writable with minimal permissions
105+ RUN mkdir -p storage/framework/cache \
106+ storage/framework/sessions \
107+ storage/framework/views \
108+ storage/logs \
109+ bootstrap/cache \
110+ public/dist \
111+ && chown -R www-data:www-data storage bootstrap/cache public/dist \
112+ && chmod -R 750 storage bootstrap/cache \
113+ && chmod -R 755 public/dist \
114+ && touch /app/frankenphp_target \
115+ && touch /app/public/dist/user.css \
116+ && touch /app/public/dist/custom.js \
117+ && chown www-data:www-data /app/public/dist/user.css /app/public/dist/custom.js \
118+ && chmod 644 /app/public/dist/user.css /app/public/dist/custom.js \
119+ && cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini \
120+ && echo "upload_max_filesize=110M" > $PHP_INI_DIR/conf.d/custom.ini \
121+ && echo "post_max_size=110M" >> $PHP_INI_DIR/conf.d/custom.ini \
122+ && echo "max_execution_time=3000" >> $PHP_INI_DIR/conf.d/custom.ini \
123+ && echo "expose_php=Off" >> $PHP_INI_DIR/conf.d/custom.ini \
124+ && echo "display_errors=Off" >> $PHP_INI_DIR/conf.d/custom.ini \
125+ && echo "log_errors=On" >> $PHP_INI_DIR/conf.d/custom.ini
126+
127+ # Copy entrypoint and validation scripts
128+ COPY docker/scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
129+ COPY docker/scripts/validate-env.sh /usr/local/bin/validate-env.sh
130+ COPY docker/scripts/create-admin-user.sh /usr/local/bin/create-admin-user.sh
131+ COPY docker/scripts/permissions-check.sh /usr/local/bin/permissions-check.sh
132+ RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/validate-env.sh /usr/local/bin/permissions-check.sh /usr/local/bin/create-admin-user.sh
133+
134+ # Expose port 8000 (Octane)
135+ EXPOSE 8000
136+
137+ # Set entrypoint
138+ ENTRYPOINT ["/usr/local/bin/entrypoint.sh" ]
139+
140+ # Default command: run Octane with FrankenPHP
141+ CMD ["php" , "artisan" , "octane:start" , "--server=frankenphp" , "--host=0.0.0.0" , "--port=8000" ]
0 commit comments