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
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
|
@ -189,6 +192,7 @@ jobs:
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|
|
||||||
12
Dockerfile
12
Dockerfile
|
|
@ -70,5 +70,13 @@ RUN chown -R www-data:www-data /app \
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
# Start supervisor
|
# Health check
|
||||||
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
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`.
|
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
|
## License
|
||||||
|
|
||||||
This work is licensed under a
|
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