2 - Add production Dockerfile and release image pipeline
All checks were successful
Build and Push Docker Image / build (push) Successful in 9m39s

This commit is contained in:
myrmidex 2026-08-23 20:55:23 +02:00
parent 49fe4bf72d
commit f267bac8e1
12 changed files with 268 additions and 55 deletions

View file

@ -2,6 +2,10 @@ node_modules/
npm-debug.log* npm-debug.log*
vendor/ vendor/
# Produced by the builder stages; never copy a local build in.
public/build/
public/hot
.env .env
.env.* .env.*
!.env.example !.env.example
@ -18,5 +22,12 @@ storage/framework/cache/*
storage/framework/sessions/* storage/framework/sessions/*
storage/framework/views/* storage/framework/views/*
# Generated on the host with dev dependencies present; packages.php would
# register providers (laravel/pail, sail, ...) that --no-dev never installs.
bootstrap/cache/*.php
tests/ tests/
README.md README.md
docs/
compose.yaml
.forgejo/

View file

@ -20,7 +20,13 @@ LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=sqlite DB_CONNECTION=mariadb
DB_HOST=db
DB_PORT=3306
DB_DATABASE=anagram
DB_USERNAME=anagram
DB_PASSWORD=
DB_ROOT_PASSWORD=
# DB_HOST=127.0.0.1 # DB_HOST=127.0.0.1
# DB_PORT=3306 # DB_PORT=3306
# DB_DATABASE=laravel # DB_DATABASE=laravel

View file

@ -0,0 +1,41 @@
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: docker
container:
image: catthehacker/ubuntu:act-latest
steps:
- uses: https://data.forgejo.org/actions/checkout@v4
- name: Set up Docker Buildx
uses: https://data.forgejo.org/docker/setup-buildx-action@v3
- name: Login to Forgejo Registry
uses: https://data.forgejo.org/docker/login-action@v3
with:
registry: forge.lvl0.xyz
username: ${{ github.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Determine tags
id: meta
run: |
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
TAG="${{ github.ref_name }}"
echo "tags=forge.lvl0.xyz/anagram-finder/web:${TAG},forge.lvl0.xyz/anagram-finder/web:latest" >> $GITHUB_OUTPUT
else
echo "tags=forge.lvl0.xyz/anagram-finder/web:latest" >> $GITHUB_OUTPUT
fi
- name: Build and push
uses: https://data.forgejo.org/docker/build-push-action@v5
with:
context: .
file: Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}

View file

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 5
groups:
github-actions:
patterns:
- "*"

View file

@ -1,38 +0,0 @@
name: tests
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2
with:
php-version: '8.3'
tools: composer:v2
coverage: none
- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '22'
- name: Setup Application
run: composer setup
- name: Run CI Checks
run: composer ci:check

1
.gitignore vendored
View file

@ -23,3 +23,4 @@ yarn-error.log
/.vscode /.vscode
/.zed /.zed
/.php-cs-fixer.cache /.php-cs-fixer.cache
compose.override.yaml

123
Dockerfile Normal file
View file

@ -0,0 +1,123 @@
# ---- vendor ----------------------------------------------------------------
FROM composer:2 AS vendor
WORKDIR /app
# anagram-finder/core is a VCS repository, so composer needs git.
RUN apk add --no-cache git
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-scripts \
--no-autoloader \
--prefer-dist \
--no-interaction
COPY . .
# --no-scripts: Laravel's post-autoload-dump hook runs artisan, which wants an
# environment that does not exist at build time. Package discovery happens on boot.
RUN composer dump-autoload --no-dev --optimize --classmap-authoritative --no-scripts
# ---- assets ----------------------------------------------------------------
FROM node:22-alpine AS assets
WORKDIR /app
# @lvl0/ui is a git dependency, so npm needs git available.
RUN apk add --no-cache git
COPY package.json package-lock.json ./
RUN npm ci
COPY vite.config.js ./
COPY resources ./resources
# app.css imports Flux's stylesheet from vendor/ and scans three vendor globs
# for class names, so the asset build needs composer's output present.
COPY --from=vendor /app/vendor ./vendor
RUN npm run build
# ---- runtime ---------------------------------------------------------------
FROM dunglas/frankenphp:1-php8.4-alpine
RUN apk add --no-cache mariadb-client curl
RUN install-php-extensions \
pdo_mysql \
opcache \
zip \
gd \
intl \
bcmath \
pcntl
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
WORKDIR /app
COPY --from=vendor /app /app
COPY --from=assets /app/public/build /app/public/build
# The dev server writes public/hot and only removes it on a clean shutdown; a
# stray copy makes @vite() point at a dev server that does not exist here.
RUN rm -f /app/public/hot
RUN cat > /etc/caddy/Caddyfile <<'EOF'
{
frankenphp
order php_server before file_server
}
:8000 {
root * /app/public
php_server {
index index.php
}
encode gzip
file_server
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
EOF
RUN cat > /start.sh <<'EOF'
#!/bin/sh
set -e
if [ -n "$DB_HOST" ]; then
echo "Waiting for database..."
until mariadb -h "$DB_HOST" -u "$DB_USERNAME" -p"$DB_PASSWORD" -e "SELECT 1" >/dev/null 2>&1; do
sleep 2
done
fi
# Rebuild the package manifest from what is actually installed. Any manifest
# baked in at build time could name a dev provider that --no-dev omitted.
rm -f bootstrap/cache/packages.php bootstrap/cache/services.php
php artisan package:discover --ansi
php artisan migrate --force
# Cached at boot rather than at build: config:cache bakes in environment values,
# which are not known until the container runs.
php artisan config:cache
php artisan route:cache
php artisan view:cache
exec frankenphp run --config /etc/caddy/Caddyfile
EOF
RUN chmod +x /start.sh
RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache
EXPOSE 8000
CMD ["/start.sh"]

69
compose.yaml Normal file
View file

@ -0,0 +1,69 @@
# Anagram Finder — self-hosting stack.
#
# Copy .env.example to .env, set APP_KEY and the database passwords, then:
# docker compose up -d
#
# The app binds to 127.0.0.1 only. Put a reverse proxy in front of it for
# anything public-facing, and set APP_URL to the address people will use.
services:
app:
image: forge.lvl0.xyz/anagram-finder/web:latest
container_name: anagram_app
restart: unless-stopped
ports:
- "127.0.0.1:8000:8000"
environment:
APP_NAME: "Anagram Finder"
APP_ENV: production
APP_DEBUG: "false"
APP_KEY: "${APP_KEY}"
APP_URL: "${APP_URL:-http://localhost:8000}"
DB_CONNECTION: mariadb
DB_HOST: db
DB_PORT: "3306"
DB_DATABASE: "${DB_DATABASE:-anagram}"
DB_USERNAME: "${DB_USERNAME:-anagram}"
DB_PASSWORD: "${DB_PASSWORD}"
SESSION_DRIVER: database
CACHE_STORE: database
QUEUE_CONNECTION: database
# A plain dependency, not condition: service_healthy — podman-compose 1.5.0
# hangs waiting on that. The app's start script polls for the database itself.
depends_on:
- db
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8000/up"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- anagram
db:
image: mariadb:11
container_name: anagram_db
restart: unless-stopped
environment:
MARIADB_DATABASE: "${DB_DATABASE:-anagram}"
MARIADB_USER: "${DB_USERNAME:-anagram}"
MARIADB_PASSWORD: "${DB_PASSWORD}"
MARIADB_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
- anagram
networks:
anagram:
driver: bridge
volumes:
db_data:

View file

@ -115,7 +115,7 @@
"repositories": [ "repositories": [
{ {
"type": "vcs", "type": "vcs",
"url": "ssh://git@forge.lvl0.xyz:2222/anagram-finder/core.git" "url": "https://forge.lvl0.xyz/anagram-finder/core.git"
} }
] ]
} }

4
composer.lock generated
View file

@ -4,14 +4,14 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "421410120a87c8f8ec2456627557bc36", "content-hash": "1b36d4e3f53a631da85cf21b05c1a2b6",
"packages": [ "packages": [
{ {
"name": "anagram-finder/core", "name": "anagram-finder/core",
"version": "v0.1.0", "version": "v0.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "ssh://git@forge.lvl0.xyz:2222/anagram-finder/core.git", "url": "https://forge.lvl0.xyz/anagram-finder/core.git",
"reference": "4b8ce74729fa9a6d0ca91c5d661256ca256e5291" "reference": "4b8ce74729fa9a6d0ca91c5d661256ca256e5291"
}, },
"require": { "require": {

12
package-lock.json generated
View file

@ -11,6 +11,9 @@
"tailwindcss": "^4.0.7", "tailwindcss": "^4.0.7",
"vite": "^8.0.0" "vite": "^8.0.0"
}, },
"devDependencies": {
"@lvl0/ui": "git+https://forge.lvl0.xyz/lvl0/ui.git#v0.1.1"
},
"optionalDependencies": { "optionalDependencies": {
"@laravel/multiplex": "^0.4.1", "@laravel/multiplex": "^0.4.1",
"@rollup/rollup-linux-x64-gnu": "4.9.5", "@rollup/rollup-linux-x64-gnu": "4.9.5",
@ -102,6 +105,15 @@
"node": ">=22.13.0" "node": ">=22.13.0"
} }
}, },
"node_modules/@lvl0/ui": {
"version": "0.1.0",
"resolved": "git+https://forge.lvl0.xyz/lvl0/ui.git#e235cec8a3cfe4060a4fda5857ac2bd1fc9bb7b1",
"dev": true,
"license": "MIT",
"peerDependencies": {
"tailwindcss": "^4.0.0"
}
},
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.146.0", "version": "0.146.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz",

View file

@ -20,6 +20,6 @@
"lightningcss-linux-x64-gnu": "^1.29.1" "lightningcss-linux-x64-gnu": "^1.29.1"
}, },
"devDependencies": { "devDependencies": {
"@lvl0/ui": "git+ssh://git@forge.lvl0.xyz:2222/lvl0/ui.git#v0.1.0" "@lvl0/ui": "git+https://forge.lvl0.xyz/lvl0/ui.git#v0.1.1"
} }
} }