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
- Prerequisites
- Initial VPS Setup
- Docker Installation
- WireGuard VPN Setup ⚠️ SETUP FIRST - APP IS VPN-ONLY
- Application Setup
- SSL Certificate Setup
- Portainer Setup
- Automated Deployment Setup
- Monitoring & Maintenance
- 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.comor192.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
-
In the WireGuard dashboard, click "New Client"
-
Give it a name (e.g., "MyLaptop")
-
Click the QR code or download the config file
-
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
-
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 withphp artisan key:generate(run after first container start)APP_URL- Your domainDB_DATABASE,DB_USERNAME,DB_PASSWORD- Strong database credentialsPGADMIN_EMAIL,PGADMIN_PASSWORD- pgAdmin credentials
3. Update Docker Compose
Edit docker-compose.yaml and update:
- Replace
example.comwith 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
Method 1: Gitea Webhooks (Recommended)
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:
- Go to Settings → Webhooks
- Add webhook:
- Payload URL:
http://your-vps-ip:9000 - Secret:
your-webhook-secret - Trigger: Push events on
mainbranch
- Payload URL:
- Test webhook
Method 2: Gitea Actions (Recommended for CI/CD)
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/domainSSH_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:
- Connect to VPN
- Open browser:
http://10.13.13.1:5050 - Login with
PGADMIN_EMAILandPGADMIN_PASSWORD - Add server:
- Host:
postgres - Port:
5432 - Database: Your
DB_DATABASE - Username: Your
DB_USERNAME - Password: Your
DB_PASSWORD
- Host:
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
.envfile 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