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:
Víctor Falcón 2025-12-29 13:41:45 +01:00 committed by Víctor Falcón
parent b52e2de987
commit 39ef165655
7 changed files with 267 additions and 2 deletions

56
.env.production.example Normal file
View File

@ -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

View File

@ -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 }}

View File

@ -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"]

View File

@ -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

View File

@ -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:

62
docker/entrypoint.sh Normal file
View File

@ -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

View File

@ -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: