# Teren App - Ubuntu 24.04 VPS Deployment Guide This guide covers the complete setup of the Teren App on Ubuntu 24.04 using Docker, including SSL, WireGuard VPN, and automated deployments. ## Table of Contents 1. [Prerequisites](#prerequisites) 2. [Initial VPS Setup](#initial-vps-setup) 3. [Docker Installation](#docker-installation) 4. [WireGuard VPN Setup](#wireguard-vpn-setup) ⚠️ **SETUP FIRST - APP IS VPN-ONLY** 5. [Application Setup](#application-setup) 6. [SSL Certificate Setup](#ssl-certificate-setup) 7. [Portainer Setup](#portainer-setup) 8. [Automated Deployment Setup](#automated-deployment-setup) 9. [Monitoring & Maintenance](#monitoring--maintenance) 10. [Troubleshooting](#troubleshooting) --- ## Prerequisites - Ubuntu 24.04 VPS with root access - Domain name pointed to your VPS IP (optional - can use IP only) - Self-hosted Gitea server - At least 2GB RAM (4GB recommended) - 20GB+ storage **IMPORTANT:** This application is configured to be accessible ONLY through WireGuard VPN. The app will NOT be publicly accessible. --- ## Initial VPS Setup ### 1. Update System ```bash sudo apt update && sudo apt upgrade -y ``` ### 2. Create Deploy User ```bash # Create user sudo adduser deployer sudo usermod -aG sudo deployer # Switch to deploy user su - deployer ``` ### 3. Configure Firewall ```bash # Install UFW sudo apt install ufw -y # Allow SSH, HTTP, HTTPS, and WireGuard sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw allow 51820/udp # WireGuard # Enable firewall sudo ufw enable sudo ufw status ``` ### 4. Install Essential Tools ```bash sudo apt install -y \ git \ curl \ wget \ unzip \ vim \ htop \ software-properties-common ``` --- ## Docker Installation ### 1. Install Docker ```bash # Add Docker's official GPG key sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add repository echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # Install Docker sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin ``` ### 2. Configure Docker ```bash # Add user to docker group sudo usermod -aG docker $USER # Enable Docker service sudo systemctl enable docker sudo systemctl start docker # Verify installation docker --version docker compose version # Log out and back in for group changes to take effect exit su - deployer ``` ---WireGuard VPN Setup ⚠️ **CRITICAL: Setup WireGuard FIRST before the application!** The app is configured to be VPN-only and will not be publicly accessible. ### 1. Enable Kernel Modules ```bash # Load WireGuard kernel module sudo apt install -y wireguard-tools sudo modprobe wireguard # Make it persistent echo "wireguard" | sudo tee -a /etc/modules ``` ### 2. Configure Environment ```bash cd /var/www/Teren-app # Edit .env file vim .env ``` Set these WireGuard variables: - `WG_SERVERURL`: Your VPS public IP or domain (e.g., `vpn.example.com` or `192.168.1.100`) - `WG_UI_PASSWORD`: Strong password for WireGuard dashboard ### 3. Start WireGuard Container ```bash # Start only WireGuard first docker compose up -d wireguard # Wait for it to initialize sleep 10 # Check status docker compose logs wireguard ``` ### 4. Access WireGuard Dashboard The WireGuard Web UI is temporarily accessible publicly for initial setup: **URL:** `http://YOUR_VPS_IP:51821` Login with the password you set in `WG_UI_PASSWORD`. ### 5. Create Your First Client 1. In the WireGuard dashboard, click **"New Client"** 2. Give it a name (e.g., "MyLaptop") 3. Click the QR code or download the config file 4. Install WireGuard on your device: - **Windows:** https://www.wireguard.com/install/ - **macOS:** App Store - **Linux:** `sudo apt install wireguard` - **iOS/Android:** App Store / Play Store 5. ImpoStart Application Containers **Note:** Make sure you're connected to the WireGuard VPN before proceeding. ```bash # Start all application containers docker compose up -d postgres redis app nginx certbot # Verify containers are running docker compose ps # Test application (from your VPN-connected machine) curl http://10.13.13.1 # Or in browser: http://10.13.13.1 You should get responses from the VPN gateway. ### 7. Secure WireGuard Dashboard (After Setup) After creating your initial clients, secure the WireGuard UI by editing `docker-compose.yaml`: **Note:** SSL certificates are optional since the app is VPN-only. You can use self-signed certificates or skip SSL entirely for internal VPN access. However, if you want proper SSL: ### Option 1: Self-Signed Certificates (Recommended for VPN-only) ```bash # Generate self-signed certificate sudo mkdir -p docker/nginx/ssl sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout docker/nginx/ssl/selfsigned.key \ -out docker/nginx/ssl/selfsigned.crt \ -subj "/C=SI/ST=Slovenia/L=Ljubljana/O=TerenApp/CN=10.13.13.1" # Update nginx config to use self-signed cert vim docker/nginx/conf.d/app.conf ``` Update SSL paths: ```nginx ssl_certificate /etc/nginx/ssl/selfsigned.crt; ssl_certificate_key /etc/nginx/ssl/selfsigned.key; ``` ```bash # Restart nginx docker compose restart nginx # Test (ignore certificate warning) curl -k https://10.13.13.1 ``` ### Option 2: Let's Encrypt (If using domain name) Only if you have a domain pointing to your VPS and want valid SSL: ```bash # Temporarily allow port 80 publicly sudo ufw allow 80/tcp # Request certificate docker compose run --rm certbot certonly \ --standalone \ --preferred-challenges http \ --email your-email@example.com \ --agree-tos \ --no-eff-email \ -d your-domain.com # Block port 80 again sudo ufw delete allow 80/tcp # Update nginx config with Let's Encrypt paths # Restart nginx docker compose restart nginx ``` ### Option 3: No SSL (HTTP Only) For internal VPN-only access, you can skip SSL entirely: ```bash # Edit nginx config to remove HTTPS server block vim docker/nginx/conf.d/app.conf # Keep only the HTTP (port 80) server block # Restart nginx docker compose restart nginx # Access via: http://10.13.13.1 ```bash # Create project directory sudo mkdir -p /var/www sudo chown -R $USER:$USER /var/www cd /var/www # Clone from your Gitea server git clone git@your-gitea-server.com:username/Teren-app.git cd Teren-app ``` ### 2. Setup Environment ```bash # Copy example files cp .env.production.example .env cp docker-compose.yaml.example docker-compose.yaml # Edit .env file with your actual values vim .env ``` **Important:** Update these values in `.env`: - `APP_KEY` - Generate with `php artisan key:generate` (run after first container start) - `APP_URL` - Your domain - `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD` - Strong database credentials - `PGADMIN_EMAIL`, `PGADMIN_PASSWORD` - pgAdmin credentials ### 3. Update Docker Compose Edit `docker-compose.yaml` and update: - Replace `example.com` with your domain in nginx volume mounts - Update environment variables as needed ### 4. Create Required Directories ```bash # Create docker directories mkdir -p docker/nginx/conf.d mkdir -p docker/nginx/ssl mkdir -p docker/certbot/conf mkdir -p docker/certbot/www mkdir -p docker/postgres/init mkdir -p docker/supervisor/conf.d mkdir -p docker/php # Set permissions sudo chown -R $USER:$USER docker/ ``` ### 5. Update Nginx Configuration Edit `docker/nginx/conf.d/app.conf` and replace `example.com` with your actual domain. ### 6. Initial SSL Certificate (HTTP-only first) Before getting SSL certificates, modify nginx config temporarily: ```bash # Comment out SSL sections in docker/nginx/conf.d/app.conf # Keep only the HTTP (port 80) server block # Start containers docker compose up -d postgres redis app nginx # Verify nginx is running docker compose ps curl http://your-domain.com # Should get some response ``` --- ## SSL Certificate Setup ### 1. Obtain Initial Certificate ```bash # Request certificate for your domain docker compose run --rm certbot certonly \ --webroot \ --webroot-path=/var/www/certbot \ --email your-email@example.com \ --agree-tos \ --no-eff-email \ -d your-domain.com \ -d www.your-domain.com ``` ### 2. Enable HTTPS in Nginx ```bash # Uncomment SSL sections in docker/nginx/conf.d/app.conf vimPortainer Setup Portainer provides a web UI for managing Docker containers, images, networks, and volumes. ### 1. Access Portainer **Prerequisites:** Must be connected to WireGuard VPN **URL:** `http://10.13.13.1:9000` or `https://10.13.13.1:9443` ### 2. Initial Setup On first access: 1. Create admin account: - Username: `admin` - Password: (set a strong password) 2. Select **"Docker"** environment 3. Click **"Connect"** ### 3. Portainer Features - **Containers:** Start, stop, restart, view logs - **Images:** Pull, build, remove images - **Networks:** Manage Docker networks - **Volumes:** View and manage volumes - **Stacks:** Deploy docker-compose stacks - **Events:** Real-time Docker events ### 4. Useful Portainer Operations **View Container Logs:** 1. Go to Containers 2. Click on container name 3. Click "Logs" 4. Select "Auto-refresh" **Execute Commands:** 1. Go to Containers 2. Click on container 3. Click "Console" 4. Select "/bin/sh" or "/bin/bash" **Update Container:** 1. Go to Containers 2. Click container name 3. Click "Recreate" 4. Enable "Pull latest image" ### 5. Security Notes - Portainer is only accessible via VPN (bound to 10.13.13.1) - Always use strong passwords - Enable 2FA if deploying Portainer Business Edition - Regularly backup Portainer data volume - "127.0.0.1:5432:5432" # To: ports: - "10.13.13.1:5432:5432" # WireGuard VPN subnet ``` Then restart: ```bash docker compose down docker compose up -d ``` ### 5. Connect Client Install WireGuard on your local machine: - **Linux:** `sudo apt install wireguard` - **macOS:** Download from App Store - **Windows:** Download from wireguard.com Import the client config and connect. ### 6. Access Services via VPN Once connected to VPN: - pgAdmin: `http://10.13.13.1:5050` - PostgreSQL: `10.13.13.1:5432` --- ## Automated Deployment Setup ### Method 1: Gitea Webhooks (Recommended) #### 1. Setup Webhook Listener on VPS ```bash # Create webhook handler script sudo vim /usr/local/bin/webhook-handler.sh ``` Add: ```bash #!/bin/bash # Simple webhook handler PORT=9000 SECRET="your-webhook-secret" while true; do echo "Listening for webhooks on port $PORT..." echo -e "HTTP/1.1 200 OK\n\n" | nc -l -p $PORT -q 1 > /tmp/webhook.log # Verify secret (basic) if grep -q "$SECRET" /tmp/webhook.log; then echo "Valid webhook received, deploying..." cd /var/www/Teren-app && ./deploy.sh >> /var/log/deploy.log 2>&1 fi done ``` ```bash # Make executable sudo chmod +x /usr/local/bin/webhook-handler.sh # Create systemd service sudo vim /etc/systemd/system/webhook-handler.service ``` Add: ```ini [Unit] Description=Gitea Webhook Handler After=network.target [Service] Type=simple User=deployer ExecStart=/usr/local/bin/webhook-handler.sh Restart=always [Install] WantedBy=multi-user.target ``` ```bash # Enable and start sudo systemctl enable webhook-handler sudo systemctl start webhook-handler # Allow webhook port in firewall sudo ufw allow 9000/tcp ``` #### 2. Configure Gitea Webhook In your Gitea repository: 1. Go to Settings → Webhooks 2. Add webhook: - Payload URL: `http://your-vps-ip:9000` - Secret: `your-webhook-secret` - Trigger: Push events on `main` branch 3. Test webhook ### Method 2: Gitea Actions (Recommended for CI/CD) #### 1. Enable Gitea Actions On your Gitea server, edit `app.ini`: ```ini [actions] ENABLED = true ``` #### 2. Setup Actions Runner on VPS ```bash # Download runner cd ~ wget https://gitea.com/gitea/act_runner/releases/latest/download/act_runner-linux-amd64 chmod +x act_runner-linux-amd64 sudo mv act_runner-linux-amd64 /usr/local/bin/act-runner # Register runner act-runner register --instance https://your-gitea.com --token YOUR_RUNNER_TOKEN # Generate config act-runner generate-config > runner-config.yaml # Create systemd service sudo vim /etc/systemd/system/act-runner.service ``` Add: ```ini [Unit] Description=Gitea Actions Runner After=network.target [Service] Type=simple User=deployer WorkingDirectory=/home/deployer ExecStart=/usr/local/bin/act-runner daemon --config /home/deployer/runner-config.yaml Restart=always [Install] WantedBy=multi-user.target ``` ```bash sudo systemctl enable act-runner sudo systemctl start act-runner ``` #### 3. Create Workflow File In your repository, create `.gitea/workflows/deploy.yaml`: ```yaml name: Deploy to Production on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy via SSH uses: appleboy/ssh-action@master with: host: ${{ secrets.VPS_HOST }} username: deployer key: ${{ secrets.SSH_PRIVATE_KEY }} script: | cd /var/www/Teren-app ./deploy.sh ``` Add secrets in Gitea: Settings → Secrets → Actions - `VPS_HOST`: Your VPS IP/domain - `SSH_PRIVATE_KEY`: SSH key for deployer user ### 3. Make Deploy Script Executable ```bash cd /var/www/Teren-app chmod +x deploy.sh # Edit script to match your setup vim deploy.sh ``` ### 4. Test Deployment ```bash # Manual test ./deploy.sh # Or trigger via git push git commit --allow-empty -m "Test deployment" git push origin main ``` --- ## Application Initialization ### 1. Start All Containers ```bash cd /var/www/Teren-app docker compose up -d ``` ### 2. Generate Application Key ```bash docker compose exec app php artisan key:generate ``` ### 3. Run Migrations ```bash docker compose exec app php artisan migrate --force ``` ### 4. Seed Database (if needed) ```bash docker compose exec app php artisan db:seed --force ``` ### 5. Cache Configuration ```bash docker compose exec app php artisan config:cache docker compose exec app php artisan route:cache docker compose exec app php artisan view:cache ``` ### 6. Set Permissions ```bash docker compose exec app chown -R www:www /var/www/storage docker compose exec app chown -R www:www /var/www/bootstrap/cache ``` ### 7. Verify Installation ```bash # Check containers docker compose ps # Check logs docker compose logs -f app # Test application curl https://your-domain.com ``` --- ## Monitoring & Maintenance ### Container Management ```bash # View all containers docker compose ps # View logs docker compose logs -f app # Laravel app docker compose logs -f nginx # Nginx docker compose logs -f postgres # Database # Restart containers docker compose restart app docker compose restart nginx # Stop all docker compose down # Start all docker compose up -d ``` ### Queue Workers ```bash # Check queue worker status docker compose exec app supervisorctl status # Restart qServices via VPN **All services are only accessible when connected to WireGuard VPN:** | Service | URL | Purpose | |---------|-----|---------| | Laravel App | `http://10.13.13.1` | Main application | | pgAdmin | `http://10.13.13.1:5050` | PostgreSQL UI | | Portainer | `http://10.13.13.1:9000` | Docker management | | WireGuard UI | `http://10.13.13.1:51821` | VPN management | | PostgreSQL | `10.13.13.1:5432` | Direct DB connection | | Redis | `10.13.13.1:6379` | Direct Redis connection | **pgAdmin Setup:** 1. Connect to VPN 2. Open: `http://10.13.13.1:5050` 3. Login with `PGADMIN_EMAIL` and `PGADMIN_PASSWORD` 4. Add server: - Host: `postgres` (or `10.13.13.1` if connecting externally)nt ```bash # Connect to PostgreSQL docker compose exec postgres psql -U teren_user -d teren_app # Backup database docker compose exec postgres pg_dump -U teren_user teren_app > backup-$(date +%Y%m%d).sql # Restore database docker compose exec -T postgres psql -U teren_user teren_app < backup-20260113.sql ``` ### Access pgAdmin Via WireGuard VPN: 1. Connect to VPN 2. Open browser: `http://10.13.13.1:5050` 3. Login with `PGADMIN_EMAIL` and `PGADMIN_PASSWORD` 4. Add server: - Host: `postgres` - Port: `5432` - Database: Your `DB_DATABASE` - Username: Your `DB_USERNAME` - Password: Your `DB_PASSWORD` ### Log Rotation ```bash # Install logrotate sudo apt install logrotate -y # Create config sudo vim /etc/logrotate.d/teren-app ``` Add: ``` /var/www/Teren-app/storage/logs/*.log { daily missingok rotate 14 compress delaycompress notifempty create 0640 deployer deployer sharedscripts } ``` ### Automated Backups Create backup script: ```bash sudo vim /usr/local/bin/backup-teren.sh ``` Add: ```bash #!/bin/bash BACKUP_DIR="/backups/teren-app" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR # Backup database docker compose -f /var/www/Teren-app/docker-compose.yaml exec -T postgres \ pg_dump -U teren_user teren_app | gzip > $BACKUP_DIR/db-$DATE.sql.gz # Backup uploads tar -czf $BACKUP_DIR/storage-$DATE.tar.gz -C /var/www/Teren-app storage/app/public # Keep only last 7 days find $BACKUP_DIR -type f -mtime +7 -delete echo "Backup completed: $DATE" ``` ```bash sudo chmod +x /usr/local/bin/backup-teren.sh # Add to crontab crontab -e ``` Add: ``` 0 2 * * * /usr/local/bin/backup-teren.sh >> /var/log/backup.log 2>&1 ``` --- ## Troubleshooting ### Container Won't Start ```bash # Check logs docker compose logs app # Check container status docker compose ps # Rebuild container docker compose down docker compose build --no-cache app docker compose up -d ``` ### Permission Errors ```bash # Fix storage permissions docker compose exec app chown -R www:www /var/www/storage docker compose exec app chmod -R 775 /var/www/storage ``` ### Database Connection Issues ```bash # Check if PostgreSQL is running docker compose ps postgres # Check PostgreSQL logs docker compose logs postgres # Test connection docker compose exec app php artisan tinker # In tinker: DB::connection()->getPdo(); ``` ### Queue Not Processing ```bash # Check supervisor status docker compose exec app supervisorctl status # Restart queue workers docker compose exec app supervisorctl restart all # Check worker logs docker compose exec app tail -f storage/logs/worker.log ``` # Check kernel module lsmod | grep wireguard # Check WireGuard interface docker compose exec wireguard wg show ``` ### Can't Access WireGuard Dashboard ```bash # Check if container is running docker compose ps wireguard # Check logs docker compose logs wireguard # Verify port binding sudo netstat -tulpn | grep 51821 # Try accessing via IP curl http://YOUR_VPS_IP:51821 ``` ### Can't Access Application After VPN Connection ```bash # Verify VPN connection ping 10.13.13.1 # Check routing table (on client) ip route | grep 10.13.13.0 # Check if services are bound correctly docker compose exec app netstat -tulpn # Check nginx logs docker compose logs nginx # Verify nginx is listening on VPN IP docker compose exec nginx netstat -tulpn | grep 80 ### SSL Certificate Issues ```bash # Check certificate expiry docker compose run --rm certbot certificates # Renew manually docker compose run --rm certbot renew # Check nginx config docker compose exec nginx nginx -t # Reload nginx docker compose restart nginx ``` ### WireGuard Not Working ```bash # Check WireGuard status docker compose logs wireguard # Restart WireGuard docker compose restart wireguard # Check firewall sudo ufw status # Verify port is open sudo ss -tulpn | grep 51820 ``` ### High Memory Usage ```bash # ChecWireGuard VPN configured and working - [ ] WireGuard UI secured (VPN-only after initial setup) - [ ] All services bound to VPN network (10.13.13.1) - [ ] Firewall configured (UFW) - only VPN port public - [ ] SSH key authentication enabled - [ ] Strong passwords for all services (DB, pgAdmin, Portainer, WireGuard) - [ ] `.env` file not in git - [ ] SSL certificates configured (self-signed or Let's Encrypt) - [ ] Regular backups automated - [ ] Log rotation configured - [ ] Security headers in nginx - [ ] Docker containers run as non-root - [ ] Regular system updates scheduled - [ ] WireGuard client configs secur ### Application Returns 500 Error ```bash # Check Laravel logs docker compose exec app tail -f storage/logs/laravel.log # Check permissions docker compose exec app ls -la storage/ # Clear caches docker compose exec app php artisan cache:clear docker compose exec app php artisan config:clear docker compose exec app php artisan route:clear docker compose exec app php artisan view:clear ``` --- ## Security Checklist - [ ] Firewall configured (UFW) - [ ] SSH key authentication enabled - [ ] Strong database passwords - [ ] `.env` file not in git - [ ] Services behind WireGuard VPN - [ ] SSL certificates valid - [ ] Regular backups automated - [ ] Log rotation configured - [ ] Security headers in nginx - [ ] Docker containers run as non-root - [ ] Regular system updates scheduled --- ## Useful Commands Reference ```bash # Deploy new version ./deploy.sh # View all logs docker compose logs -f # Restart everything docker compose restart # Update single container docker compose up -d --no-deps --build app # Run artisan commands docker compose exec app php artisan [command] # Access container shell docker compose exec app sh # Database backup docker compose exec postgres pg_dump -U teren_user teren_app > backup.sql # System monitoring htop docker stats df -h ``` --- ## Support & Updates For issues specific to the Teren App, check the application logs and supervisor status. For Docker/infrastructure issues, check container logs and system logs. To update the deployment guide or Docker configuration, submit changes via Gitea and they'll be automatically deployed. --- **Last Updated:** January 2026 **Version:** 1.0.0