Add production Docker setup for easy self-hosting with the CI-built image (#42)
- Add production Docker setup for easy self-hosting with the CI-built image - Build multi-architecture Docker images (amd64 + arm64) ### Changes Docker Production Setup - Add docker-compose.production.yml for running the production image locally - Add docker/entrypoint.sh with automatic setup (migrations, caching, storage, APP_KEY generation) - Add HEALTHCHECK to Dockerfile for container health monitoring - Add .env.production.example with documented production defaults ### CI/CD - Add QEMU for multi-arch builds - Build Docker images for both linux/amd64 and linux/arm64 ### Coolify / WIP - Add templates/coolify/whisper-money.yaml one-click template - Includes MySQL service with health checks - Auto-generates credentials using Coolify's SERVICE_* variables ### Documentation - Update README with production Docker instructions - Add environment variable reference table
This commit is contained in:
parent
b52e2de987
commit
39ef165655
|
|
@ -0,0 +1,56 @@
|
|||
# ===========================================
|
||||
# Whisper Money - Production Environment
|
||||
# ===========================================
|
||||
# Copy this file to .env and configure for your production environment
|
||||
|
||||
# Application
|
||||
APP_NAME="Whisper Money"
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_URL=https://your-domain.com
|
||||
APP_KEY= # Auto-generated on first startup if empty
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
# Logging
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_LEVEL=error
|
||||
|
||||
# Database (MySQL)
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=mysql
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=whisper_money
|
||||
DB_USERNAME=whisper_money
|
||||
DB_PASSWORD=your-secure-password
|
||||
|
||||
# Cache & Sessions (Redis runs inside the container)
|
||||
CACHE_STORE=redis
|
||||
SESSION_DRIVER=redis
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=true
|
||||
|
||||
# Queue
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
# Redis (internal to container - no configuration needed)
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Email - Resend (Recommended for production)
|
||||
MAIL_MAILER=resend
|
||||
RESEND_API_KEY=your-resend-api-key
|
||||
MAIL_FROM_ADDRESS=hi@your-domain.com
|
||||
MAIL_FROM_NAME="Whisper Money"
|
||||
|
||||
# Stripe (Optional - for subscriptions)
|
||||
STRIPE_KEY=
|
||||
STRIPE_SECRET=
|
||||
STRIPE_WEBHOOK_SECRET=
|
||||
SUBSCRIPTIONS_ENABLED=false
|
||||
|
||||
# UI Configuration
|
||||
HIDE_AUTH_BUTTONS=false
|
||||
|
|
@ -166,6 +166,9 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
|
@ -189,6 +192,7 @@ jobs:
|
|||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
|
|
|||
12
Dockerfile
12
Dockerfile
|
|
@ -70,5 +70,13 @@ RUN chown -R www-data:www-data /app \
|
|||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Start supervisor
|
||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
|
||||
CMD curl -f http://localhost/up || exit 1
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Run entrypoint script (handles migrations, caching, then starts supervisor)
|
||||
CMD ["/entrypoint.sh"]
|
||||
|
|
|
|||
31
README.md
31
README.md
|
|
@ -80,6 +80,37 @@ This will concurrently start:
|
|||
|
||||
The application will be available at `https://whispermoney.test`.
|
||||
|
||||
## Running with Docker (Production Image)
|
||||
|
||||
For testing the production Docker image locally:
|
||||
|
||||
1. **Copy the production environment file:**
|
||||
|
||||
```bash
|
||||
cp .env.production.example .env
|
||||
```
|
||||
|
||||
2. **Start the services:**
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.production.yml up -d
|
||||
```
|
||||
|
||||
The application will be available at `http://localhost:8080`.
|
||||
|
||||
To use a different port, set `APP_PORT`:
|
||||
|
||||
```bash
|
||||
APP_PORT=3000 docker compose -f docker-compose.production.yml up -d
|
||||
```
|
||||
|
||||
## Other Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ----------------------- | ------- | ------------------------------------------- |
|
||||
| `HIDE_AUTH_BUTTONS` | `false` | Hide login/register buttons on landing page |
|
||||
| `SUBSCRIPTIONS_ENABLED` | `false` | Enable Stripe subscriptions |
|
||||
|
||||
## License
|
||||
|
||||
This work is licensed under a
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
services:
|
||||
app:
|
||||
image: ${WHISPER_IMAGE:-ghcr.io/whisper-money/whisper-money:latest}
|
||||
ports:
|
||||
- "${APP_PORT:-8080}:80"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- DB_HOST=mysql
|
||||
- DB_PORT=3306
|
||||
volumes:
|
||||
- whisper-storage:/app/storage
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
|
||||
MYSQL_DATABASE: ${DB_DATABASE}
|
||||
MYSQL_USER: ${DB_USERNAME}
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- whisper-mysql:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
whisper-storage:
|
||||
whisper-mysql:
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=== Whisper Money Container Startup ==="
|
||||
|
||||
# Ensure storage directories exist (volume may be empty on first deploy)
|
||||
echo "Ensuring storage directories exist..."
|
||||
mkdir -p /app/storage/app/public
|
||||
mkdir -p /app/storage/framework/cache/data
|
||||
mkdir -p /app/storage/framework/sessions
|
||||
mkdir -p /app/storage/framework/views
|
||||
mkdir -p /app/storage/logs
|
||||
chown -R www-data:www-data /app/storage
|
||||
chmod -R 775 /app/storage
|
||||
|
||||
# Auto-generate APP_KEY if not set
|
||||
if [ -z "$APP_KEY" ]; then
|
||||
echo "No APP_KEY found, generating one..."
|
||||
APP_KEY=$(php artisan key:generate --show)
|
||||
export APP_KEY
|
||||
echo "Generated APP_KEY: $APP_KEY"
|
||||
echo ""
|
||||
echo "=================================================="
|
||||
echo "IMPORTANT: Save this key in your environment!"
|
||||
echo "APP_KEY=$APP_KEY"
|
||||
echo "=================================================="
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Wait for MySQL to be ready
|
||||
echo "Waiting for MySQL..."
|
||||
max_attempts=30
|
||||
attempt=0
|
||||
until php artisan db:monitor --databases=mysql > /dev/null 2>&1 || [ $attempt -ge $max_attempts ]; do
|
||||
attempt=$((attempt + 1))
|
||||
echo "MySQL not ready (attempt $attempt/$max_attempts)..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $attempt -ge $max_attempts ]; then
|
||||
echo "Warning: MySQL may not be ready, proceeding anyway..."
|
||||
fi
|
||||
|
||||
# Run migrations
|
||||
echo "Running migrations..."
|
||||
php artisan migrate --force
|
||||
|
||||
# Cache configuration
|
||||
echo "Caching configuration..."
|
||||
php artisan config:cache
|
||||
php artisan route:cache
|
||||
php artisan view:cache
|
||||
php artisan event:cache
|
||||
|
||||
# Create storage link (harmless if already exists)
|
||||
echo "Creating storage link..."
|
||||
php artisan storage:link 2>/dev/null || true
|
||||
|
||||
echo "=== Startup complete, launching services ==="
|
||||
|
||||
# Start supervisor
|
||||
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# documentation: https://github.com/whisper-money/whisper-money
|
||||
# slogan: Privacy-first personal finance with end-to-end encryption
|
||||
# tags: finance, personal-finance, budgeting, privacy, encryption, laravel, react
|
||||
# logo: svgs/whisper-money.svg
|
||||
# port: 80
|
||||
|
||||
services:
|
||||
whisper-money:
|
||||
image: ghcr.io/whisper-money/whisper-money:latest
|
||||
environment:
|
||||
- SERVICE_FQDN_WHISPERMONEY_80
|
||||
- APP_NAME=Whisper Money
|
||||
- APP_ENV=production
|
||||
- APP_DEBUG=false
|
||||
- APP_KEY=${SERVICE_BASE64_64_APPKEY}
|
||||
- APP_URL=${SERVICE_FQDN_WHISPERMONEY_80}
|
||||
- DB_CONNECTION=mysql
|
||||
- DB_HOST=mysql
|
||||
- DB_PORT=3306
|
||||
- DB_DATABASE=${MYSQL_DATABASE:-whisper_money}
|
||||
- DB_USERNAME=${SERVICE_USER_MYSQL}
|
||||
- DB_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- CACHE_STORE=redis
|
||||
- SESSION_DRIVER=redis
|
||||
- QUEUE_CONNECTION=database
|
||||
- REDIS_HOST=127.0.0.1
|
||||
- HIDE_AUTH_BUTTONS=${HIDE_AUTH_BUTTONS:-false}
|
||||
- MAIL_MAILER=${MAIL_MAILER:-log}
|
||||
- MAIL_HOST=${MAIL_HOST:-}
|
||||
- MAIL_PORT=${MAIL_PORT:-587}
|
||||
- MAIL_USERNAME=${MAIL_USERNAME:-}
|
||||
- MAIL_PASSWORD=${MAIL_PASSWORD:-}
|
||||
- MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS:-hi@whisper.money}
|
||||
- MAIL_FROM_NAME=${MAIL_FROM_NAME:-Whisper Money}
|
||||
- RESEND_API_KEY=${RESEND_API_KEY:-}
|
||||
- STRIPE_KEY=${STRIPE_KEY:-}
|
||||
- STRIPE_SECRET=${STRIPE_SECRET:-}
|
||||
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET:-}
|
||||
- SUBSCRIPTIONS_ENABLED=${SUBSCRIPTIONS_ENABLED:-false}
|
||||
volumes:
|
||||
- whisper-money-storage:/app/storage
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost/up"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE:-whisper_money}
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
volumes:
|
||||
- whisper-money-mysql:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${SERVICE_PASSWORD_MYSQLROOT}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
whisper-money-storage:
|
||||
whisper-money-mysql:
|
||||
Loading…
Reference in New Issue