# TowerOps Production Deployment Guide This guide covers deploying TowerOps to production with TimescaleDB, email notifications, and monitoring capabilities. ## Prerequisites - PostgreSQL 14+ with TimescaleDB extension installed - Elixir 1.19+ and Erlang 27+ - SSL certificate for HTTPS - SMTP server for email notifications ## Environment Variables Create a `.env.prod` file or configure these environment variables in your deployment platform: ### Required ```bash # Secret key for encryption (generate with: mix phx.gen.secret) SECRET_KEY_BASE=your-secret-key-here # Database URL DATABASE_URL=postgresql://user:password@host:port/towerops_prod # Application URL PHX_HOST=towerops.example.com PORT=4000 # Environment MIX_ENV=prod ``` ### Email Configuration ```bash # SMTP settings for alert notifications SMTP_RELAY=smtp.example.com SMTP_USERNAME=your-smtp-username SMTP_PASSWORD=your-smtp-password SMTP_PORT=587 SMTP_TLS=true # From address for emails ALERT_FROM_EMAIL=alerts@towerops.example.com ALERT_FROM_NAME="TowerOps Alerts" ``` ### Optional ```bash # Custom check interval (default: 300 seconds / 5 minutes) DEFAULT_CHECK_INTERVAL_SECONDS=300 # TimescaleDB retention (default: 90 days) MONITORING_RETENTION_DAYS=90 # TimescaleDB compression (default: 7 days) MONITORING_COMPRESSION_DAYS=7 ``` ## Database Setup **Important**: TimescaleDB must be installed on your PostgreSQL server. See [TIMESCALEDB.md](TIMESCALEDB.md) for installation instructions. ### 1. Install TimescaleDB Extension Connect to your PostgreSQL database and enable TimescaleDB: ```sql CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; ``` ### 2. Create Database ```bash # Development mix ecto.create # Production MIX_ENV=prod mix ecto.create ``` ### 3. Run Migrations ```bash # Development mix ecto.migrate # Production MIX_ENV=prod mix ecto.migrate ``` This will: - Create all necessary tables - Convert `monitoring_checks` to TimescaleDB hypertable - Set up retention policies (auto-delete data older than 90 days) - Configure compression policies (compress data older than 7 days) - Create continuous aggregates for dashboard performance ## Application Configuration ### Production Config Update `config/runtime.exs` with your production settings. Key configurations: #### Database Pool Size ```elixir config :towerops, Towerops.Repo, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10") ``` #### Mailer Configuration The application uses Swoosh for email delivery. Update the mailer configuration: ```elixir config :towerops, Towerops.Mailer, adapter: Swoosh.Adapters.SMTP, relay: System.get_env("SMTP_RELAY"), username: System.get_env("SMTP_USERNAME"), password: System.get_env("SMTP_PASSWORD"), port: String.to_integer(System.get_env("SMTP_PORT") || "587"), tls: :always ``` ## Building for Production ### 1. Install Dependencies ```bash mix deps.get --only prod ``` ### 2. Compile Application ```bash MIX_ENV=prod mix compile ``` ### 3. Build Assets ```bash MIX_ENV=prod mix assets.deploy ``` This will: - Build and minify CSS with Tailwind v4 - Bundle JavaScript with esbuild - Generate digested asset files ### 4. Create Release ```bash MIX_ENV=prod mix release ``` The release will be created in `_build/prod/rel/towerops/`. ## Deployment ### Using Releases ```bash # Start the release _build/prod/rel/towerops/bin/towerops start # Run as daemon _build/prod/rel/towerops/bin/towerops daemon # Check status _build/prod/rel/towerops/bin/towerops pid # Stop _build/prod/rel/towerops/bin/towerops stop ``` ### Using systemd Create `/etc/systemd/system/towerops.service`: ```ini [Unit] Description=TowerOps Monitoring Service After=network.target postgresql.service [Service] Type=forking User=towerops Group=towerops WorkingDirectory=/opt/towerops Environment="PORT=4000" Environment="MIX_ENV=prod" ExecStart=/opt/towerops/_build/prod/rel/towerops/bin/towerops daemon ExecStop=/opt/towerops/_build/prod/rel/towerops/bin/towerops stop Restart=on-failure RestartSec=5 SyslogIdentifier=towerops [Install] WantedBy=multi-user.target ``` Enable and start: ```bash sudo systemctl daemon-reload sudo systemctl enable towerops sudo systemctl start towerops sudo systemctl status towerops ``` ### Using Docker Create `Dockerfile`: ```dockerfile FROM elixir:1.19-alpine AS build RUN apk add --no-cache build-base git nodejs npm WORKDIR /app # Install hex and rebar RUN mix local.hex --force && \ mix local.rebar --force ENV MIX_ENV=prod # Install dependencies COPY mix.exs mix.lock ./ RUN mix deps.get --only prod # Copy application COPY config config COPY lib lib COPY priv priv COPY assets assets # Build assets RUN mix assets.deploy # Compile and build release RUN mix compile RUN mix release # Runtime stage FROM alpine:3.18 RUN apk add --no-cache libstdc++ openssl ncurses-libs WORKDIR /app COPY --from=build /app/_build/prod/rel/towerops ./ ENV HOME=/app ENV PORT=4000 ENV MIX_ENV=prod CMD ["bin/towerops", "start"] ``` Build and run: ```bash docker build -t towerops . docker run -d \ --name towerops \ -p 4000:4000 \ --env-file .env.prod \ towerops ``` ## Monitoring System The monitoring system starts automatically when the application boots. It will: 1. **Start Equipment Monitors**: Create a GenServer worker for each equipment with `monitoring_enabled: true` 2. **Ping Equipment**: Each worker pings its equipment at the configured interval 3. **Record Checks**: Results are stored in `monitoring_checks` TimescaleDB hypertable 4. **Create Alerts**: Status changes trigger alert creation 5. **Send Emails**: Owners and admins receive email notifications for down/up events 6. **Broadcast Updates**: Real-time UI updates via Phoenix PubSub ### Monitoring Behavior by Environment - **All Environments** (dev/test/prod): - TimescaleDB features enabled (hypertables, retention, compression, aggregates) - Monitoring system active - **Production (`MIX_ENV=prod`)**: - Email notifications sent to organization owners/admins - **Development/Test**: - Email notifications disabled (to avoid test database connection issues) - Monitoring system runs but emails are suppressed ## SSL/TLS Configuration ### Using Nginx Reverse Proxy ```nginx upstream towerops { server 127.0.0.1:4000; } server { listen 80; server_name towerops.example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name towerops.example.com; ssl_certificate /etc/letsencrypt/live/towerops.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/towerops.example.com/privkey.pem; location / { proxy_pass http://towerops; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ## Production Checklist ### Pre-Deployment - [ ] Database created with TimescaleDB extension installed - [ ] All environment variables configured - [ ] SSL certificate obtained and configured - [ ] SMTP server configured and tested - [ ] Assets built and digested - [ ] Release created successfully ### Post-Deployment - [ ] Application starts without errors - [ ] Database migrations ran successfully - [ ] TimescaleDB hypertables created (`\d+ monitoring_checks` shows hypertable) - [ ] Continuous aggregates created (`SELECT * FROM timescaledb_information.continuous_aggregates;`) - [ ] Can access application via HTTPS - [ ] User registration works - [ ] Can create organization - [ ] Can add sites and equipment - [ ] Monitoring workers start for enabled equipment - [ ] Ping checks are being recorded - [ ] Alerts are created on status changes - [ ] Email notifications are sent - [ ] LiveView updates work in real-time ### Monitoring - [ ] Check application logs for errors - [ ] Monitor database connections - [ ] Monitor memory usage (GenServer workers) - [ ] Check email delivery logs - [ ] Verify TimescaleDB compression is working - [ ] Verify retention policies are cleaning old data ## Database Maintenance ### Verify TimescaleDB Status ```sql -- Check hypertables SELECT * FROM timescaledb_information.hypertables; -- Check compression SELECT * FROM timescaledb_information.chunks WHERE is_compressed = true; -- Check continuous aggregates SELECT * FROM timescaledb_information.continuous_aggregates; -- Check retention policies SELECT * FROM timescaledb_information.jobs WHERE proc_name = 'policy_retention'; ``` ### Manual Compression ```sql -- Compress specific chunk SELECT compress_chunk('_timescaledb_internal._hyper_1_1_chunk'); -- Compress all chunks older than 7 days SELECT compress_chunk(chunk) FROM ( SELECT show_chunks('monitoring_checks', older_than => INTERVAL '7 days') AS chunk ) AS chunks; ``` ### Backup and Restore ```bash # Backup (includes TimescaleDB metadata) pg_dump -Fc -d towerops_prod > towerops_backup.dump # Restore pg_restore -d towerops_prod towerops_backup.dump ``` ## Scaling Considerations ### Database - Monitor `monitoring_checks` table size and growth rate - Adjust retention policy if needed (default: 90 days) - Consider read replicas for heavy dashboard usage - TimescaleDB compression reduces storage by 90%+ ### Application - Scale horizontally by running multiple instances - Use load balancer with sticky sessions for LiveView - Monitor GenServer worker memory usage - Consider distributed Erlang if needed ### Email - Use transactional email service (SendGrid, Postmark, etc.) for reliability - Implement rate limiting if needed - Monitor email delivery failures ## Troubleshooting ### Monitoring Not Working Check logs for GenServer errors: ```bash tail -f /var/log/towerops/error.log ``` Verify equipment has monitoring enabled: ```sql SELECT id, name, monitoring_enabled, check_interval_seconds FROM equipment; ``` ### TimescaleDB Not Active Verify extension is loaded: ```sql \dx timescaledb SELECT * FROM timescaledb_information.hypertables; ``` If missing: 1. Ensure TimescaleDB is installed (see TIMESCALEDB.md) 2. Drop and recreate the database: `mix ecto.drop && mix ecto.create && mix ecto.migrate` ### Email Not Sending Test SMTP connection: ```elixir # In iex -S mix Swoosh.Email.new() |> Swoosh.Email.to("test@example.com") |> Swoosh.Email.from({"TowerOps", "alerts@towerops.example.com"}) |> Swoosh.Email.subject("Test") |> Swoosh.Email.text_body("Test email") |> Towerops.Mailer.deliver() ``` Check environment variables are set correctly. ## Support For issues or questions: - Check logs in `_build/prod/rel/towerops/logs/` - Review TimescaleDB documentation: https://docs.timescale.com/ - Review Phoenix deployment guide: https://hexdocs.pm/phoenix/deployment.html