feat: Docker dev env with Caddy, PHP on host (#103)
## Summary - Replaces Valet/Traefik with Docker-based infrastructure: Caddy (HTTPS reverse proxy), MySQL, Redis, Mailhog - PHP runs natively on the host via `composer run dev` — Caddy proxies to `host.docker.internal:8000` - PHP container available under `--profile full` for self-hosting via `setup.sh` - Vite dev server serves over HTTPS using mkcert certs to avoid mixed-content issues - `.env.example` defaults updated for host-based dev workflow (127.0.0.1 + forwarded ports) - Deletes obsolete `traefik/` directory ## Test plan - [x] `docker compose up -d` starts caddy, mysql, redis, mailhog (NOT php) - [x] `composer run dev` serves PHP on :8000, Vite on :5173 with HTTPS - [x] `https://whisper.money.local` loads correctly via Caddy - [x] Vite HMR works (edit a React component, see hot reload) - [x] `php artisan test --compact` passes - [x] `docker compose --profile full up -d` also starts the PHP container
This commit is contained in:
parent
b5fd11efaf
commit
caccac6166
21
.env.example
21
.env.example
|
|
@ -21,8 +21,8 @@ LOG_DEPRECATIONS_CHANNEL=null
|
|||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3307
|
||||
DB_DATABASE=whisper_money
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
|
@ -42,13 +42,13 @@ CACHE_STORE=database
|
|||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_HOST=redis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
REDIS_PORT=6380
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hi@whisper.money"
|
||||
|
|
@ -94,6 +94,15 @@ SENTRY_LARAVEL_DSN=
|
|||
SENTRY_TRACES_SAMPLE_RATE=1.0
|
||||
SENTRY_PROFILES_SAMPLE_RATE=0
|
||||
|
||||
# Docker Service Ports (forwarded to host)
|
||||
FORWARD_DB_PORT=3307
|
||||
FORWARD_REDIS_PORT=6380
|
||||
FORWARD_MAILHOG_PORT=1025
|
||||
FORWARD_MAILHOG_DASHBOARD_PORT=8025
|
||||
|
||||
# Dev Mode (set to false for full Docker deployment via setup.sh)
|
||||
DEV_MODE=true
|
||||
|
||||
# Demo Account Configuration
|
||||
DEMO_EMAIL=demo@whisper.money
|
||||
DEMO_PASSWORD=demo
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ yarn-error.log
|
|||
/.vscode
|
||||
/.zed
|
||||
.php-cs-fixer.cache
|
||||
/traefik/certs/*.pem
|
||||
/docker/caddy/certs/*.pem
|
||||
/docker/caddy/certs/*-key.pem
|
||||
.claude/settings.local.json
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ whisper.money.local {
|
|||
# These certificates are automatically trusted by browsers
|
||||
tls /etc/caddy/certs/whisper.money.local.pem /etc/caddy/certs/whisper.money.local-key.pem
|
||||
|
||||
reverse_proxy php:8000
|
||||
reverse_proxy host.docker.internal:8000
|
||||
|
||||
header {
|
||||
-Server
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ services:
|
|||
networks:
|
||||
- sail
|
||||
php:
|
||||
profiles:
|
||||
- full
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
],
|
||||
"dev": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --queue=default,emails\" \"php artisan pail --timeout=0\" \"bun run dev\" \"stripe listen --forward-to https://whispermoney.test/stripe/webhook\" --names=server,queue,logs,vite --kill-others"
|
||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --queue=default,emails\" \"php artisan pail --timeout=0\" \"bun run dev\" \"stripe listen --forward-to https://whisper.money.local/stripe/webhook\" --names=server,queue,logs,vite --kill-others"
|
||||
],
|
||||
"dev:ssr": [
|
||||
"bun run build:ssr",
|
||||
|
|
|
|||
|
|
@ -790,7 +790,7 @@ start_services() {
|
|||
fi
|
||||
|
||||
echo -e "${BLUE}Starting Docker services...${NC}"
|
||||
docker compose up -d
|
||||
docker compose --profile full up -d
|
||||
|
||||
wait_for_service "mysql"
|
||||
wait_for_service "redis"
|
||||
|
|
@ -939,7 +939,7 @@ install() {
|
|||
|
||||
# Now start PHP service (after dependencies are installed)
|
||||
echo -e "${BLUE}Starting PHP service...${NC}"
|
||||
if ! docker compose up -d php; then
|
||||
if ! docker compose --profile full up -d php; then
|
||||
echo -e "${RED}Failed to start PHP service!${NC}"
|
||||
echo -e "${YELLOW}Checking logs...${NC}"
|
||||
docker compose logs php 2>&1 | tail -20
|
||||
|
|
@ -1217,7 +1217,7 @@ upgrade() {
|
|||
|
||||
# Restart services
|
||||
echo -e "${BLUE}Restarting services...${NC}"
|
||||
docker compose restart
|
||||
docker compose --profile full restart
|
||||
echo -e "${GREEN}Services restarted.${NC}"
|
||||
echo ""
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import {
|
|||
import { useEncryptionKey } from '@/contexts/encryption-key-context';
|
||||
import { useLocale } from '@/hooks/use-locale';
|
||||
import { transactionSyncService } from '@/services/transaction-sync';
|
||||
import { type Transaction } from '@/types/transaction';
|
||||
import { type ParsedTransaction } from '@/types/import';
|
||||
import { type Transaction } from '@/types/transaction';
|
||||
import { formatDateMedium } from '@/utils/date';
|
||||
import { __ } from '@/utils/i18n';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
http:
|
||||
routers:
|
||||
whispermoney-http:
|
||||
rule: Host(`whispermoney.test`)
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
- redirect-to-https
|
||||
service: whispermoney
|
||||
whispermoney-https:
|
||||
rule: Host(`whispermoney.test`)
|
||||
entryPoints:
|
||||
- websecure
|
||||
service: whispermoney
|
||||
middlewares:
|
||||
- headers-secure
|
||||
tls: {}
|
||||
|
||||
middlewares:
|
||||
redirect-to-https:
|
||||
redirectScheme:
|
||||
scheme: https
|
||||
permanent: true
|
||||
headers-secure:
|
||||
headers:
|
||||
customRequestHeaders:
|
||||
X-Forwarded-Proto: https
|
||||
customResponseHeaders:
|
||||
X-Forwarded-Proto: https
|
||||
|
||||
services:
|
||||
whispermoney:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://host.docker.internal:8000
|
||||
passHostHeader: true
|
||||
|
||||
tls:
|
||||
certificates:
|
||||
- certFile: /etc/traefik/certs/whispermoney.test.pem
|
||||
keyFile: /etc/traefik/certs/whispermoney.test-key.pem
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if ! command -v mkcert &> /dev/null; then
|
||||
echo "mkcert is not installed. Installing..."
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
if command -v brew &> /dev/null; then
|
||||
brew install mkcert
|
||||
brew install nss
|
||||
else
|
||||
echo "Please install Homebrew first, then run: brew install mkcert nss"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Please install mkcert manually. Visit: https://github.com/FiloSottile/mkcert"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$(mkcert -CAROOT)/rootCA.pem" ]; then
|
||||
echo "Installing local CA..."
|
||||
mkcert -install
|
||||
fi
|
||||
|
||||
echo "Generating certificates for whispermoney.test..."
|
||||
cd "$(dirname "$0")/certs"
|
||||
mkcert -key-file whispermoney.test-key.pem -cert-file whispermoney.test.pem whispermoney.test
|
||||
|
||||
echo "Certificates generated successfully!"
|
||||
echo "Files created:"
|
||||
echo " - traefik/certs/whispermoney.test.pem"
|
||||
echo " - traefik/certs/whispermoney.test-key.pem"
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { wayfinder } from '@laravel/vite-plugin-wayfinder';
|
||||
import { sentryVitePlugin } from '@sentry/vite-plugin';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
|
@ -9,6 +12,18 @@ export default defineConfig({
|
|||
build: {
|
||||
sourcemap: true,
|
||||
},
|
||||
server: (() => {
|
||||
const certPath = path.resolve(__dirname, 'docker/caddy/certs/whisper.money.local.pem');
|
||||
const keyPath = path.resolve(__dirname, 'docker/caddy/certs/whisper.money.local-key.pem');
|
||||
if (fs.existsSync(certPath) && fs.existsSync(keyPath)) {
|
||||
return {
|
||||
https: { cert: certPath, key: keyPath },
|
||||
host: '0.0.0.0',
|
||||
hmr: { host: 'whisper.money.local' },
|
||||
};
|
||||
}
|
||||
return {};
|
||||
})(),
|
||||
plugins: [
|
||||
laravel({
|
||||
input: ['resources/css/app.css', 'resources/js/app.tsx'],
|
||||
|
|
|
|||
Loading…
Reference in New Issue