# Database Deployment Guide ActaLog supports three database backends with Docker deployment: - **SQLite** - Single-file database (default, best for single-server) - **PostgreSQL** - Production-ready SQL database (recommended for multi-instance) - **MariaDB/MySQL** - Alternative production SQL database ## Quick Start by Database ### SQLite (Default - Simplest) **Best for:** Development, single-server production, small-scale deployments ```bash cd docker # Use default compose file cp .env.example .env vim .env # Set GITHUB_OWNER, TAG, JWT_SECRET # Start docker compose up -d ``` **Pros:** - No separate database container - Zero configuration - Automatic backups (single file) - Perfect for embedded deployments **Cons:** - Single connection writer - Not ideal for high-concurrency --- ### PostgreSQL (Recommended for Production) **Best for:** Production deployments, high concurrency, data integrity ```bash cd docker # Use PostgreSQL compose file cp .env.postgres .env vim .env # Set passwords and JWT_SECRET # Start PostgreSQL + ActaLog docker compose -f docker-compose.postgres.yml up -d # View logs docker compose -f docker-compose.postgres.yml logs -f # Check health curl http://localhost:8080/health ``` **Configuration (.env):** ```env GITHUB_OWNER=yourusername TAG=latest DB_NAME=actalog DB_USER=actalog DB_PASSWORD=super_secure_password_here JWT_SECRET=your_jwt_secret_here ``` **Pros:** - Best for concurrent users - ACID compliance - Advanced features (JSON, full-text search) - Battle-tested in production **Cons:** - Requires separate container - More memory usage (~50MB) **Connection String:** ``` postgresql://actalog:password@postgres:5432/actalog?sslmode=disable ``` --- ### MariaDB (Alternative Production Option) **Best for:** Teams familiar with MySQL, existing MySQL infrastructure ```bash cd docker # Use MariaDB compose file cp .env.mariadb .env vim .env # Set passwords and JWT_SECRET # Start MariaDB + ActaLog docker compose -f docker-compose.mariadb.yml up -d # View logs docker compose -f docker-compose.mariadb.yml logs -f # Check health curl http://localhost:8080/health ``` **Configuration (.env):** ```env GITHUB_OWNER=yourusername TAG=latest DB_NAME=actalog DB_USER=actalog DB_PASSWORD=super_secure_password_here DB_ROOT_PASSWORD=different_root_password_here JWT_SECRET=your_jwt_secret_here ``` **Pros:** - MySQL-compatible - Good performance - Wide ecosystem support - Familiar to many developers **Cons:** - Slightly more resource usage than PostgreSQL - Less advanced JSON support **Connection String:** ``` mysql://actalog:password@mariadb:3306/actalog ``` --- ## Database Comparison | Feature | SQLite | PostgreSQL | MariaDB | |---------|--------|------------|---------| | Setup Complexity | ⭐ Easiest | ⭐⭐ Moderate | ⭐⭐ Moderate | | Concurrent Users | 1-10 | 100+ | 100+ | | Memory Usage | ~5MB | ~50MB | ~60MB | | Backup | Copy file | pg_dump | mysqldump | | JSON Support | Limited | Excellent | Good | | Full-text Search | Basic | Advanced | Good | | Recommended For | Dev, Small | Production | Production | --- ## Migration Between Databases ### SQLite → PostgreSQL ```bash # 1. Export from SQLite sqlite3 actalog.db .dump > dump.sql # 2. Convert to PostgreSQL format (manual) # - Change AUTOINCREMENT to SERIAL # - Fix datetime formats # - Adjust syntax # 3. Import to PostgreSQL docker compose -f docker-compose.postgres.yml exec postgres \ psql -U actalog -d actalog -f /tmp/dump.sql ``` ### Using ActaLog Backup/Restore ActaLog has built-in backup endpoints that work across databases: ```bash # 1. Create backup on SQLite deployment curl -X POST http://localhost:8080/api/admin/backups \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"description":"Pre-migration backup"}' # 2. Download backup curl http://localhost:8080/api/admin/backups/1/download \ -H "Authorization: Bearer $TOKEN" \ -o backup.zip # 3. Deploy with PostgreSQL docker compose -f docker-compose.postgres.yml up -d # 4. Restore backup curl -X POST http://localhost:8080/api/admin/backups/restore \ -H "Authorization: Bearer $TOKEN" \ -F "file=@backup.zip" ``` --- ## Database Maintenance ### PostgreSQL Maintenance ```bash # Enter PostgreSQL shell docker compose -f docker-compose.postgres.yml exec postgres \ psql -U actalog -d actalog # Vacuum (clean up) VACUUM ANALYZE; # Check database size SELECT pg_size_pretty(pg_database_size('actalog')); # List tables \dt # Backup docker compose -f docker-compose.postgres.yml exec postgres \ pg_dump -U actalog actalog > backup.sql # Restore docker compose -f docker-compose.postgres.yml exec -T postgres \ psql -U actalog -d actalog < backup.sql ``` ### MariaDB Maintenance ```bash # Enter MariaDB shell docker compose -f docker-compose.mariadb.yml exec mariadb \ mysql -u actalog -p actalog # Check database size SELECT table_schema AS "Database", ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS "Size (MB)" FROM information_schema.TABLES WHERE table_schema = 'actalog' GROUP BY table_schema; # Optimize tables OPTIMIZE TABLE users, workouts, workout_movements; # Backup docker compose -f docker-compose.mariadb.yml exec mariadb \ mysqldump -u actalog -p actalog > backup.sql # Restore docker compose -f docker-compose.mariadb.yml exec -T mariadb \ mysql -u actalog -p actalog < backup.sql ``` ### SQLite Maintenance ```bash # Backup (just copy the file) docker compose cp actalog:/app/data/actalog.db ./backup.db # Restore docker compose cp ./backup.db actalog:/app/data/actalog.db docker compose restart actalog # Check database size docker compose exec actalog ls -lh /app/data/actalog.db # Vacuum (in container) docker compose exec actalog sqlite3 /app/data/actalog.db "VACUUM;" ``` --- ## External Database (Not in Docker) You can use ActaLog with an existing database server running on: - **Host machine** (accessed via `host.docker.internal` or host IP) - **Another server** on your network (accessed via IP/hostname) - **Cloud database** (RDS, Cloud SQL, etc.) This is useful when you: - Already have a database server running - Want to share a database between multiple applications - Need database features not available in containers - Prefer to manage the database separately ### Option 1: Using Host Database Server The Docker container can connect to a database server running on the host machine. #### MariaDB/MySQL on Host **1. Configure host database for remote connections:** Edit MariaDB config (e.g., `/etc/mysql/mariadb.conf.d/50-server.cnf`): ```ini [mysqld] bind-address = 0.0.0.0 # Or specific IP ``` Restart MariaDB: ```bash sudo systemctl restart mariadb ``` **2. Create database and user:** ```sql CREATE DATABASE actalog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'actalog'@'%' IDENTIFIED BY 'yourpassword'; GRANT ALL PRIVILEGES ON actalog.* TO 'actalog'@'%'; FLUSH PRIVILEGES; ``` **3. Create docker-compose.yml for external host database:** ```yaml # docker-compose.external-host.yml version: '3.8' services: actalog: image: ghcr.io/yourusername/actalog:latest container_name: actalog restart: unless-stopped ports: - "8080:8080" environment: # Connect to host database - DB_DRIVER=mysql - DB_HOST=host.docker.internal # Docker Desktop (Mac/Windows) # - DB_HOST=172.17.0.1 # Linux (default bridge IP) # - DB_HOST=192.168.1.100 # Or use host's actual IP - DB_PORT=3306 - DB_NAME=actalog - DB_USER=actalog - DB_PASSWORD=${DB_PASSWORD} # Application config - JWT_SECRET=${JWT_SECRET} - APP_URL=${APP_URL:-http://localhost:8080} - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:8080} # Optional: automatic seed import - ADMIN_EMAIL=${ADMIN_EMAIL:-} - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} volumes: - actalog-uploads:/app/uploads # No database container needed! volumes: actalog-uploads: ``` **4. Create .env file:** ```env GITHUB_OWNER=yourusername TAG=latest DB_PASSWORD=yourpassword JWT_SECRET=your_secure_random_string_here APP_URL=https://al.fluidgrid.site CORS_ORIGINS=https://al.fluidgrid.site ``` **5. Start ActaLog:** ```bash docker compose -f docker-compose.external-host.yml up -d ``` **6. Verify connection:** ```bash # Check logs for successful database connection docker compose logs -f # Should see: # Database initialized successfully # Server listening on :8080 ``` #### PostgreSQL on Host Same process, but with PostgreSQL-specific configuration: **1. Configure PostgreSQL for remote connections:** Edit `postgresql.conf`: ```ini listen_addresses = '*' ``` Edit `pg_hba.conf`: ``` # Allow connections from Docker containers host actalog actalog 172.17.0.0/16 md5 ``` Restart PostgreSQL: ```bash sudo systemctl restart postgresql ``` **2. Create database and user:** ```sql CREATE DATABASE actalog; CREATE USER actalog WITH PASSWORD 'yourpassword'; GRANT ALL PRIVILEGES ON DATABASE actalog TO actalog; ``` **3. Update docker-compose.yml:** ```yaml environment: - DB_DRIVER=postgres - DB_HOST=host.docker.internal # Or host IP - DB_PORT=5432 - DB_NAME=actalog - DB_USER=actalog - DB_PASSWORD=${DB_PASSWORD} - DB_SSLMODE=disable # Or 'require' for production ``` ### Option 2: Using Database on Another Server If your database is on a different machine on the network: ```yaml # docker-compose.external-network.yml version: '3.8' services: actalog: image: ghcr.io/yourusername/actalog:latest restart: unless-stopped ports: - "8080:8080" environment: # MariaDB on network - DB_DRIVER=mysql - DB_HOST=192.168.1.100 # Database server IP - DB_PORT=3306 - DB_NAME=actalog - DB_USER=actalog - DB_PASSWORD=${DB_PASSWORD} # OR PostgreSQL on network # - DB_DRIVER=postgres # - DB_HOST=db.example.com # Can use hostname # - DB_PORT=5432 # App config - JWT_SECRET=${JWT_SECRET} - APP_URL=${APP_URL} - CORS_ORIGINS=${CORS_ORIGINS} volumes: - actalog-uploads:/app/uploads volumes: actalog-uploads: ``` Ensure the database server: - Allows connections from the Docker host IP - Has appropriate firewall rules (port 3306/5432 open) - Has database and user created with proper permissions ### Troubleshooting External Databases #### "Connection refused" errors This error typically means: 1. The database isn't listening on the right interface 2. The container can't reach the host network 3. Firewall is blocking the connection **Step 1: Check database is listening on all interfaces (not just localhost)** For MariaDB/MySQL: ```bash # Check current bind-address setting sudo grep -E "bind-address|skip-networking" /etc/mysql/mariadb.conf.d/*.cnf # If it shows bind-address = 127.0.0.1, change to 0.0.0.0 sudo vim /etc/mysql/mariadb.conf.d/50-server.cnf # Set: bind-address = 0.0.0.0 # Restart MariaDB sudo systemctl restart mariadb ``` For PostgreSQL: ```bash # Check postgresql.conf sudo grep listen_addresses /etc/postgresql/*/main/postgresql.conf # Should be: listen_addresses = '*' # Check pg_hba.conf allows Docker network sudo grep "172.17" /etc/postgresql/*/main/pg_hba.conf # Should have: host actalog actalog 172.17.0.0/16 md5 sudo systemctl restart postgresql ``` **Step 2: Verify database is listening** ```bash # Check if database is listening on all interfaces sudo netstat -tlnp | grep 3306 # MySQL/MariaDB sudo netstat -tlnp | grep 5432 # PostgreSQL # Should show 0.0.0.0:3306 (not 127.0.0.1:3306) ``` **Step 3: Test connectivity from container** ```bash # Test from within container docker exec -it actalog sh -c "nc -zv host.docker.internal 3306" # Or install client tools docker exec -it actalog sh apk add mariadb-client mysql -h host.docker.internal -u actalog -p actalog ``` #### "host.docker.internal" not resolving (Linux) On Linux, `host.docker.internal` doesn't work by default unlike Mac/Windows Docker Desktop. **Recommended: Add extra_hosts to docker-compose.yml** ```yaml services: actalog: image: ghcr.io/johnzastrow/actalog:latest extra_hosts: - "host.docker.internal:host-gateway" # This makes it work on Linux environment: - DB_DRIVER=mysql - DB_HOST=host.docker.internal - DB_PORT=3306 - DB_NAME=acta - DB_USER=acta - DB_PASSWORD=${DB_PASSWORD} ``` **Alternative 1: Use Docker bridge IP** ```yaml environment: - DB_HOST=172.17.0.1 # Default Docker bridge gateway ``` **Alternative 2: Use actual host IP** ```yaml environment: - DB_HOST=192.168.1.100 # Replace with your host's actual IP ``` #### Database user lacks permission from Docker network The database user may only have permission to connect from localhost, not from the Docker network. **MariaDB/MySQL - Grant permissions:** ```sql -- For Docker bridge network (more secure) GRANT ALL PRIVILEGES ON acta.* TO 'acta'@'172.17.%' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES; -- Or allow from any host (less secure, but easier) GRANT ALL PRIVILEGES ON acta.* TO 'acta'@'%' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES; -- Check grants SHOW GRANTS FOR 'acta'@'%'; ``` **PostgreSQL - Update pg_hba.conf:** ```bash # Add line to pg_hba.conf host acta acta 172.17.0.0/16 md5 # Reload PostgreSQL sudo systemctl reload postgresql ``` #### Firewall blocking Docker network (connection times out) If the connection times out (not refused), the firewall may be blocking Docker's network: ```bash # Check UFW status sudo ufw status # Allow MariaDB from Docker network sudo ufw allow from 172.17.0.0/16 to any port 3306 sudo ufw reload # For PostgreSQL sudo ufw allow from 172.17.0.0/16 to any port 5432 sudo ufw reload ``` #### Permission denied errors **MariaDB/MySQL:** ```sql -- Check user grants SHOW GRANTS FOR 'actalog'@'%'; -- Recreate user if needed DROP USER 'actalog'@'%'; CREATE USER 'actalog'@'%' IDENTIFIED BY 'yourpassword'; GRANT ALL PRIVILEGES ON actalog.* TO 'actalog'@'%'; FLUSH PRIVILEGES; ``` **PostgreSQL:** ```sql -- Check user permissions \du actalog -- Grant permissions GRANT ALL PRIVILEGES ON DATABASE actalog TO actalog; GRANT ALL ON SCHEMA public TO actalog; ``` ### When to Use External vs Containerized Database | Use External Database When | Use Containerized Database When | |---------------------------|--------------------------------| | Already have DB server running | Starting fresh deployment | | Multiple apps share database | Single application | | Need specific DB version/config | Want zero-configuration setup | | Manage backups separately | Want Docker-managed volumes | | Database on dedicated hardware | All-in-one Docker deployment | --- ## Troubleshooting ### "Connection refused" errors **PostgreSQL:** ```bash # Check if PostgreSQL is ready docker compose -f docker-compose.postgres.yml exec postgres pg_isready # Check logs docker compose -f docker-compose.postgres.yml logs postgres ``` **MariaDB:** ```bash # Check if MariaDB is ready docker compose -f docker-compose.mariadb.yml exec mariadb \ healthcheck.sh --connect # Check logs docker compose -f docker-compose.mariadb.yml logs mariadb ``` ### "database locked" with SQLite This happens with high concurrency. Consider migrating to PostgreSQL: ```bash docker compose -f docker-compose.postgres.yml up -d ``` ### Lost database password **PostgreSQL:** ```bash # Reset password docker compose -f docker-compose.postgres.yml exec postgres \ psql -U actalog -c "ALTER USER actalog PASSWORD 'new_password';" ``` **MariaDB:** ```bash # Reset password docker compose -f docker-compose.mariadb.yml exec mariadb \ mysql -u root -p -e "SET PASSWORD FOR 'actalog'@'%' = PASSWORD('new_password');" ``` --- ## Performance Tuning ### PostgreSQL Add to compose file under `postgres.command`: ```yaml command: - postgres - -c - max_connections=200 - -c - shared_buffers=256MB - -c - effective_cache_size=1GB ``` ### MariaDB Add to compose file under `mariadb.command`: ```yaml command: - --max-connections=200 - --innodb-buffer-pool-size=256M - --innodb-log-file-size=64M ``` --- ## Recommendation by Use Case | Use Case | Database | Reason | |----------|----------|--------| | Personal use | SQLite | Simple, zero-config | | Small team (< 10) | SQLite or PostgreSQL | Low overhead | | Production team | PostgreSQL | Best concurrency | | High traffic | PostgreSQL | Proven at scale | | Existing MySQL infra | MariaDB | Easy integration | | Edge/embedded | SQLite | Single file, portable | | Multi-region | PostgreSQL | Replication support | --- ## Seed Data Import ActaLog includes comprehensive seed data: - **182 movements** (all CrossFit movements including Girl/Hero WOD movements) - **314 WODs** (all benchmark Girl and Hero WODs) ### Automatic Import (Recommended) To automatically import seed data on first deployment: 1. **Set admin credentials in `.env`** before starting: ```env ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=YourSecurePassword123 ``` 2. **Start the application:** ```bash docker compose up -d ``` 3. **Register your first user** with the same email/password from `.env` 4. **Seeds import automatically** on first startup if credentials are set The import script: - Runs only once (creates marker file `/app/data/.seeds_imported`) - Skips if admin credentials are not set - Logs progress to container logs: `docker compose logs -f` ### Manual Import (Alternative) If you prefer to import manually or didn't set credentials: 1. **Via Web UI** (easiest): - Login as admin - Navigate to Import page - Upload `/app/seeds/movements.csv` - Upload `/app/seeds/wods.csv` 2. **Via API**: ```bash # Get admin token TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@example.com","password":"YourPassword"}' \ | jq -r '.access_token') # Import movements docker compose cp actalog:/app/seeds/movements.csv ./movements.csv curl -X POST http://localhost:8080/api/import/movements/confirm \ -H "Authorization: Bearer $TOKEN" \ -F "file=@movements.csv" \ -F "skip_duplicates=true" # Import WODs docker compose cp actalog:/app/seeds/wods.csv ./wods.csv curl -X POST http://localhost:8080/api/import/wods/confirm \ -H "Authorization: Bearer $TOKEN" \ -F "file=@wods.csv" \ -F "skip_duplicates=true" ``` ### Checking Import Status ```bash # Check if seeds were imported docker compose exec actalog ls -la /app/data/.seeds_imported # View import logs docker compose logs actalog | grep -i seed ``` --- ## Quick Commands ```bash # SQLite deployment docker compose up -d # PostgreSQL deployment docker compose -f docker-compose.postgres.yml up -d # MariaDB deployment docker compose -f docker-compose.mariadb.yml up -d # Stop any deployment docker compose down # or add -f # View logs docker compose logs -f # Backup database docker compose exec actalog wget -O- http://localhost:8080/api/admin/backups/1/download ```