Teren-app/DEPLOYMENT_GUIDE.md
Simon Pocrnjič df6c3133ec docker setup
2026-01-14 17:33:31 +01:00

22 KiB

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
  2. Initial VPS Setup
  3. Docker Installation
  4. WireGuard VPN Setup ⚠️ SETUP FIRST - APP IS VPN-ONLY
  5. Application Setup
  6. SSL Certificate Setup
  7. Portainer Setup
  8. Automated Deployment Setup
  9. Monitoring & Maintenance
  10. 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

sudo apt update && sudo apt upgrade -y

2. Create Deploy User

# Create user
sudo adduser deployer
sudo usermod -aG sudo deployer

# Switch to deploy user
su - deployer

3. Configure Firewall

# 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

sudo apt install -y \
    git \
    curl \
    wget \
    unzip \
    vim \
    htop \
    software-properties-common

Docker Installation

1. Install Docker

# 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

# 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

# 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

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

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

  5. ImpoStart Application Containers

Note: Make sure you're connected to the WireGuard VPN before proceeding.

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

ssl_certificate /etc/nginx/ssl/selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
# 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:

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

# 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

# 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

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

# 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

# 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

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

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

1. Setup Webhook Listener on VPS

# Create webhook handler script
sudo vim /usr/local/bin/webhook-handler.sh

Add:

#!/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
# Make executable
sudo chmod +x /usr/local/bin/webhook-handler.sh

# Create systemd service
sudo vim /etc/systemd/system/webhook-handler.service

Add:

[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
# 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

1. Enable Gitea Actions

On your Gitea server, edit app.ini:

[actions]
ENABLED = true

2. Setup Actions Runner on VPS

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

[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
sudo systemctl enable act-runner
sudo systemctl start act-runner

3. Create Workflow File

In your repository, create .gitea/workflows/deploy.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

cd /var/www/Teren-app
chmod +x deploy.sh

# Edit script to match your setup
vim deploy.sh

4. Test Deployment

# 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

cd /var/www/Teren-app
docker compose up -d

2. Generate Application Key

docker compose exec app php artisan key:generate

3. Run Migrations

docker compose exec app php artisan migrate --force

4. Seed Database (if needed)

docker compose exec app php artisan db:seed --force

5. Cache Configuration

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

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

# Check containers
docker compose ps

# Check logs
docker compose logs -f app

# Test application
curl https://your-domain.com

Monitoring & Maintenance

Container Management

# 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

# 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

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

sudo vim /usr/local/bin/backup-teren.sh

Add:

#!/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"
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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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