diff --git a/.github/scripts/rename-template.py b/.github/scripts/rename-template.py
new file mode 100644
index 0000000..119d09e
--- /dev/null
+++ b/.github/scripts/rename-template.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""Rename template placeholders when a new repo is created from the template."""
+
+import os
+import re
+
+
+def to_kebab(name: str) -> str:
+ return name.lower().replace("_", "-")
+
+
+def to_snake(name: str) -> str:
+ return name.lower().replace("-", "_")
+
+
+def to_title(name: str) -> str:
+ return " ".join(word.capitalize() for word in re.split(r"[-_]", name))
+
+
+def to_pascal(name: str) -> str:
+ return "".join(word.capitalize() for word in re.split(r"[-_]", name))
+
+
+def sanitize_docker(name: str) -> str:
+ if name[0].isdigit():
+ name = "app-" + name
+ return re.sub(r"[^a-z0-9-]", "-", name)
+
+
+def main():
+ repo_full = os.environ.get("GITHUB_REPOSITORY", "")
+ if not repo_full:
+ raise RuntimeError("GITHUB_REPOSITORY not set")
+
+ repo_name = repo_full.split("/")[-1]
+ kebab = sanitize_docker(to_kebab(repo_name))
+ snake = to_snake(repo_name)
+ title = to_title(repo_name)
+ pascal = to_pascal(repo_name)
+
+ replacements = [
+ (
+ "compose.yaml",
+ [
+ (r"^ frankenphp:\n", f" {kebab}:\n"),
+ (r"^ container_name: frankenphp\n", f" container_name: {kebab}\n"),
+ ],
+ ),
+ (
+ ".devcontainer/devcontainer.json",
+ [
+ ('"name": "PHP Starter Kit"', f'"name": "{title}"'),
+ ('"service": "frankenphp"', f'"service": "{kebab}"'),
+ ],
+ ),
+ (
+ "makefile",
+ [
+ (r"exec --user=robbyte frankenphp", f"exec --user=robbyte {kebab}"),
+ (r"exec frankenphp cat", f"exec {kebab} cat"),
+ ],
+ ),
+ (
+ "build/prod/docker-entrypoint.sh",
+ [
+ ('echo "PHP Starter Kit - Starting..."', f'echo "{title} - Starting..."'),
+ ],
+ ),
+ (
+ "src/public/index.php",
+ [
+ ("
PHP Starter Kit — Setup Wizard", f"{title} — Setup Wizard"),
+ ('PHP Starter Kit
', f'{title}
'),
+ ("PHP Starter Kit ©", f"{title} ©"),
+ ],
+ ),
+ (
+ "README.md",
+ [
+ ("# PHP Starter Kit", f"# {title}"),
+ ("https://github.com/rdurica/php_starter_kit.git", f"https://github.com/{repo_full}.git"),
+ ("https://github.com/rdurica/php_starter_kit/actions", f"https://github.com/{repo_full}/actions"),
+ ("cd php_starter_kit", f"cd {repo_name}"),
+ ],
+ ),
+ (
+ "AGENTS.md",
+ [
+ ("# Agent Context for PHP Starter Kit", f"# Agent Context for {title}"),
+ ("docker compose exec frankenphp", f"docker compose exec {kebab}"),
+ ],
+ ),
+ ]
+
+ for filepath, rules in replacements:
+ if not os.path.exists(filepath):
+ print(f"Warning: {filepath} not found, skipping")
+ continue
+
+ with open(filepath, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ original = content
+ for pattern, replacement in rules:
+ flags = re.MULTILINE if pattern.startswith("^") else 0
+ content = re.sub(pattern, replacement, content, flags=flags)
+
+ if content != original:
+ with open(filepath, "w", encoding="utf-8") as f:
+ f.write(content)
+ print(f"Updated: {filepath}")
+ else:
+ print(f"No changes: {filepath}")
+
+ with open(".template-configured", "w", encoding="utf-8") as f:
+ f.write(f"Configured from template for {repo_full}\n")
+ print("Created: .template-configured")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 29287bf..b86200f 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -25,9 +25,6 @@ jobs:
- name: Validate CI Compose config
run: docker compose -f compose.ci.yaml config
- - name: Validate demo Compose config
- run: docker compose -f compose.demo.yaml config
-
tests:
name: Framework Tests
runs-on: ubuntu-latest
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 7f1deec..d30aaaa 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -25,9 +25,6 @@ jobs:
- name: Validate CI Compose config
run: docker compose -f compose.ci.yaml config
- - name: Validate demo Compose config
- run: docker compose -f compose.demo.yaml config
-
frontend:
name: Frontend Quality
runs-on: ubuntu-latest
diff --git a/.github/workflows/template-setup.yml b/.github/workflows/template-setup.yml
new file mode 100644
index 0000000..4ea0891
--- /dev/null
+++ b/.github/workflows/template-setup.yml
@@ -0,0 +1,51 @@
+name: Template Setup
+
+on:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: write
+
+jobs:
+ rename:
+ name: Rename from template
+ runs-on: ubuntu-latest
+ if: github.repository != 'rdurica/php_starter_kit'
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Check if already configured
+ id: check
+ run: |
+ if [ -f ".template-configured" ]; then
+ echo "already_configured=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "already_configured=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Run rename script
+ if: steps.check.outputs.already_configured == 'false'
+ run: python3 .github/scripts/rename-template.py
+
+ - name: Commit and push renamed files
+ if: steps.check.outputs.already_configured == 'false'
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add -A
+ git commit -m "chore: rename project from template [skip ci]"
+ git push
+
+ - name: Remove template setup files
+ if: steps.check.outputs.already_configured == 'false'
+ run: |
+ git rm .github/workflows/template-setup.yml
+ git rm -rf .github/scripts
+ git commit -m "chore: remove template setup workflow [skip ci]"
+ git push
diff --git a/README.md b/README.md
index b3fd975..47a06a0 100755
--- a/README.md
+++ b/README.md
@@ -48,7 +48,7 @@ This starter kit provides a ready-to-use, out-of-the-box local development envir
- **FrankenPHP** — Modern PHP application server with HTTP/2, HTTP/3, and automatic HTTPS
- **Secure by default** — Non-root user, security headers, hardened sessions, no `expose_php`
-- **Multi-environment** — Dev, CI, and Demo configurations
+- **Multi-environment** — Dev and CI configurations
- **Multi-stage production build** — Minimal attack surface, optimized layers
- **CI/CD ready** — GitHub Actions with code quality, tests, security scanning
- **DevContainer support** — VSCode remote containers out of the box
@@ -109,11 +109,6 @@ After installation, you can delete `/setup.php` via the success dialog or manual
- SQLite in-memory for fast tests
- Ideal for GitHub Actions
-### Demo (`compose.demo.yaml`)
-- Self-contained stack with PostgreSQL and Redis
-- Uses prebuilt GHCR image
-- Port 8080 on host
-
## Production
Build the production image:
@@ -122,12 +117,6 @@ Build the production image:
docker build -f build/prod/Dockerfile -t myapp:latest .
```
-Run with the demo stack:
-
-```bash
-docker compose -f compose.demo.yaml up
-```
-
## CI/CD
Three GitHub Actions workflows are included:
diff --git a/compose.demo.yaml b/compose.demo.yaml
deleted file mode 100644
index 23ac0c8..0000000
--- a/compose.demo.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-services:
- app:
- image: php-starter-kit-demo:local
- build:
- context: .
- dockerfile: build/prod/Dockerfile
- env_file: demo.env
- ports:
- - "8080:80"
- depends_on:
- postgres:
- condition: service_healthy
- redis:
- condition: service_healthy
-
- postgres:
- image: postgres:17-alpine
- environment:
- POSTGRES_DB: starter_kit
- POSTGRES_USER: starter_kit
- POSTGRES_PASSWORD: starter_kit_demo_only
- volumes:
- - pgdata:/var/lib/postgresql/data
- healthcheck:
- test: ["CMD-SHELL", "pg_isready -U starter_kit"]
- interval: 5s
- timeout: 5s
- retries: 5
-
- redis:
- image: redis:8-alpine
- healthcheck:
- test: ["CMD", "redis-cli", "ping"]
- interval: 5s
- timeout: 5s
- retries: 5
-
-volumes:
- pgdata:
diff --git a/demo.env b/demo.env
deleted file mode 100644
index 2c7d1a6..0000000
--- a/demo.env
+++ /dev/null
@@ -1,40 +0,0 @@
-# Demo-only local defaults. Replace every secret before any real deployment.
-APP_NAME=PHPStarterKit
-APP_ENV=production
-APP_KEY=base64:ZGVtby1vbmx5LWxvY2FsLWtleS0zMi1ieXRlcyEhISE=
-APP_DEBUG=false
-APP_URL=http://localhost:8080
-
-APP_LOCALE=en
-APP_FALLBACK_LOCALE=en
-
-LOG_CHANNEL=stderr
-LOG_LEVEL=warning
-
-DB_CONNECTION=pgsql
-DB_HOST=postgres
-DB_PORT=5432
-DB_DATABASE=starter_kit
-DB_USERNAME=starter_kit
-DB_PASSWORD=starter_kit_demo_only
-
-SESSION_DRIVER=redis
-SESSION_LIFETIME=120
-SESSION_ENCRYPT=true
-SESSION_PATH=/
-SESSION_DOMAIN=
-
-BROADCAST_CONNECTION=log
-FILESYSTEM_DISK=local
-QUEUE_CONNECTION=redis
-
-CACHE_STORE=redis
-
-REDIS_CLIENT=predis
-REDIS_HOST=redis
-REDIS_PASSWORD=
-REDIS_PORT=6379
-
-MAIL_MAILER=log
-
-VITE_APP_NAME="${APP_NAME}"