Files
preregister/documentation/DEPLOYMENT-STRATEGY.md

13 KiB

PreRegister — Development & Deployment Strategy

Overview

┌──────────────┐     git push     ┌──────────────┐    deploy.sh    ┌──────────────────┐
│  Local Mac   │ ──────────────►  │    Gitea      │ ─────────────► │  VPS DirectAdmin │
│  (Cursor)    │                  │  (Git remote) │                │  (Production)    │
│              │                  │               │                │                  │
│  Docker:     │                  │  Tags:        │                │  Apache/PHP-FPM  │
│  - MySQL     │                  │  v1.0.0       │                │  MySQL/MariaDB   │
│  - phpMyAdmin│                  │  v1.0.1       │                │  Node (fnm)      │
│  - Mailpit   │                  │  v1.1.0       │                │                  │
└──────────────┘                  └──────────────┘                └──────────────────┘

1. Local Development (Mac + Cursor)

Directory structure

~/Projects/preregister/          ← Laravel project root
├── .cursorrules
├── .env                         ← local dev config (NOT committed)
├── .env.example                 ← template (committed)
├── .gitignore
├── docker-compose.yml           ← local dev services
├── Makefile
├── deploy.sh                    ← deployment script (committed)
├── PreRegister-Development-Prompt.md
└── ...

Daily workflow

# Start of day
cd ~/Projects/preregister
docker compose up -d            # Start MySQL, phpMyAdmin, Mailpit
make dev                        # Start Laravel + Vite dev servers

# Open Cursor, code away...

# End of day
git add -A
git commit -m "feat: description of what you built"
git push origin main

2. Git Setup with Gitea

Initial setup (once)

cd ~/Projects/preregister

# Initialize git
git init
git branch -M main

# Add your Gitea remote
git remote add origin https://your-gitea-server.nl/bert/preregister.git

# First push
git add -A
git commit -m "feat: initial project setup"
git push -u origin main

.gitignore additions

Make sure these are in .gitignore (Laravel ships most of these, but verify):

# Laravel defaults
/vendor/
/node_modules/
/public/build/
/public/hot
/public/storage
/storage/*.key
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log

# Docker
mysql_data/

# IDE
.idea/
.vscode/
*.swp

# OS
.DS_Store
Thumbs.db

# Compiled assets are built on the server
/public/build/

.env.example (committed, safe)

APP_NAME=PreRegister
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost:8000

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=preregister
DB_USERNAME=preregister
DB_PASSWORD=preregister

QUEUE_CONNECTION=database

MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1026

3. Versioning Strategy

Use Semantic Versioning (SemVer): vMAJOR.MINOR.PATCH

Version part When to bump Example
PATCH Bug fixes, small tweaks v1.0.0 → v1.0.1
MINOR New feature, backward compatible v1.0.1 → v1.1.0
MAJOR Breaking changes, major rewrite v1.1.0 → v2.0.0

Creating a release

When a version is ready to deploy:

# 1. Make sure everything is committed and pushed
git add -A
git commit -m "fix: final changes for v1.0.0"
git push origin main

# 2. Tag the release
git tag -a v1.0.0 -m "v1.0.0 - Initial release with pre-registration pages and Mailwizz sync"
git push origin v1.0.0

In Gitea you can also create releases via the web UI (Releases → New Release) which lets you add release notes.

Branch strategy (keep it simple)

main        ← production-ready code, always deployable

You're the sole developer — one branch is fine. If you ever want to experiment without risk:

git checkout -b feature/some-experiment
# ... work ...
git checkout main
git merge feature/some-experiment
git branch -d feature/some-experiment

4. VPS Server Setup (one-time)

4.1 SSH access

# From your Mac, set up SSH key access (if not already)
ssh-copy-id user@your-vps.nl

# Test
ssh user@your-vps.nl

4.2 Install Node.js via fnm

ssh user@your-vps.nl

# Install fnm
curl -fsSL https://fnm.vercel.app/install | bash

# Add to shell profile (add to ~/.bashrc or ~/.bash_profile)
echo 'eval "$(fnm env)"' >> ~/.bashrc
source ~/.bashrc

# Install Node 20 LTS
fnm install 20
fnm default 20

# Verify
node -v    # v20.x.x
npm -v     # 10.x.x

4.3 Install Composer (if not present)

cd ~
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version

4.4 Create the application directory

# Project lives OUTSIDE public_html
mkdir -p /home/username/preregister

# Symlink public/ to the domain's document root
# Adjust the path to match your DirectAdmin domain structure
ln -sfn /home/username/preregister/public /home/username/domains/preregister.yourdomain.nl/public_html

4.5 Clone the repository

cd /home/username
git clone https://your-gitea-server.nl/bert/preregister.git
cd preregister

# Install dependencies
composer install --no-dev --optimize-autoloader
npm ci

# Build frontend assets
npm run build

# Create .env for production
cp .env.example .env
nano .env   # Edit with production values (see section 5)

# Generate app key
php artisan key:generate

# Run migrations
php artisan migrate --force

# Storage link
php artisan storage:link

# Cache everything
php artisan config:cache
php artisan route:cache
php artisan view:cache

4.6 Apache .htaccess

Laravel ships with a public/.htaccess that works with Apache. Verify mod_rewrite is enabled (it usually is on DirectAdmin).

4.7 Set up cron for queue worker + scheduler

In DirectAdmin → Cron Jobs, add:

# Laravel scheduler (every minute)
* * * * * cd /home/username/preregister && php artisan schedule:run >> /dev/null 2>&1

# Queue worker - process one job per run (every minute)
* * * * * cd /home/username/preregister && php artisan queue:work --once --queue=mailwizz >> /dev/null 2>&1

4.8 Directory permissions

cd /home/username/preregister

# Storage and cache must be writable by the web server
chmod -R 775 storage bootstrap/cache
chown -R username:username .

# If Apache runs as a different user (e.g., apache, www-data, nobody):
# Check with: ps aux | grep apache
# Then set group accordingly:
# chgrp -R apache storage bootstrap/cache

5. Production .env

APP_NAME=PreRegister
APP_ENV=production
APP_KEY=                          # generated with php artisan key:generate
APP_DEBUG=false                   # NEVER true in production
APP_URL=https://preregister.yourdomain.nl

LOG_CHANNEL=daily
LOG_LEVEL=warning

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=preregister_prod
DB_USERNAME=preregister_prod
DB_PASSWORD=STRONG_PASSWORD_HERE

QUEUE_CONNECTION=database

SESSION_DRIVER=database
SESSION_LIFETIME=120

MAIL_MAILER=smtp
MAIL_HOST=your-smtp-server
MAIL_PORT=587
MAIL_USERNAME=your-smtp-user
MAIL_PASSWORD=your-smtp-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@yourdomain.nl"
MAIL_FROM_NAME="${APP_NAME}"

Create the production database via DirectAdmin → MySQL Management:

  • Database: preregister_prod
  • User: preregister_prod
  • Strong password

6. Deployment Script

This is the script you run on the VPS to deploy a new version.

Save this as deploy.sh in the project root (committed to git):

#!/bin/bash
set -e

# ──────────────────────────────────────────
# PreRegister Deploy Script
# Run on VPS: ./deploy.sh [tag]
# Examples:
#   ./deploy.sh              → deploys latest main
#   ./deploy.sh v1.2.0       → deploys specific tag
# ──────────────────────────────────────────

APP_DIR="/home/username/preregister"
TAG="${1:-}"

echo "══════════════════════════════════════"
echo "  PreRegister — Deploy"
echo "══════════════════════════════════════"

cd "$APP_DIR"

# 1. Maintenance mode
echo "→ Enabling maintenance mode..."
php artisan down --retry=30 || true

# 2. Pull latest code
echo "→ Pulling from Gitea..."
git fetch --all --tags

if [ -n "$TAG" ]; then
    echo "→ Checking out tag: $TAG"
    git checkout "$TAG"
else
    echo "→ Checking out latest main"
    git checkout main
    git pull origin main
fi

# 3. Install PHP dependencies
echo "→ Installing Composer dependencies..."
composer install --no-dev --optimize-autoloader --no-interaction

# 4. Install Node dependencies and build
echo "→ Installing npm packages..."
npm ci --production=false

echo "→ Building frontend assets..."
npm run build

# 5. Run migrations
echo "→ Running migrations..."
php artisan migrate --force

# 6. Clear and rebuild caches
echo "→ Clearing caches..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

# 7. Restart queue (process any pending jobs with new code)
echo "→ Restarting queue workers..."
php artisan queue:restart

# 8. Storage link (idempotent)
php artisan storage:link 2>/dev/null || true

# 9. Disable maintenance mode
echo "→ Going live!"
php artisan up

echo ""
echo "══════════════════════════════════════"
if [ -n "$TAG" ]; then
    echo "  Deployed: $TAG"
else
    echo "  Deployed: main (latest)"
fi
echo "══════════════════════════════════════"

Make it executable:

chmod +x deploy.sh

7. Deployment Workflow (day-to-day)

When a release is ready:

On your Mac:

# 1. Commit and push all changes
git add -A
git commit -m "feat: add subscriber export"
git push origin main

# 2. Tag the release
git tag -a v1.0.0 -m "Initial release"
git push origin v1.0.0

On the VPS (via SSH):

ssh user@your-vps.nl
cd /home/username/preregister

# Deploy a specific version
./deploy.sh v1.0.0

# Or deploy latest main (for quick fixes)
./deploy.sh

That's it. One command deploys everything: pulls code, installs dependencies, builds assets, runs migrations, clears caches, and brings the site back up.

Quick reference

Local                    Gitea                   VPS
─────                    ─────                   ───
code in Cursor
  ↓
git commit + push  ───►  main branch
  ↓
git tag v1.x.x     ───►  tag stored
                                          ssh into VPS
                                            ↓
                                          ./deploy.sh v1.x.x
                                            ↓
                                          git pull + composer + npm + build + migrate
                                            ↓
                                          live! ✓

8. Rollback Strategy

If a deploy goes wrong:

# Deploy the previous known-good tag
./deploy.sh v1.0.0

# If migrations need to be rolled back (careful!)
php artisan migrate:rollback --step=1

To see available tags:

git tag -l --sort=-version:refname

9. File Overview

File Committed Location Purpose
.env NO local + VPS Environment-specific config
.env.example YES repo Template for .env
.cursorrules YES repo Cursor AI coding rules
.gitignore YES repo Files excluded from git
docker-compose.yml YES repo Local dev services
Makefile YES repo Local dev shortcuts
deploy.sh YES repo VPS deployment script
PreRegister-Development-Prompt.md YES repo Full app specification
public/build/ NO built on VPS Compiled CSS/JS
vendor/ NO installed on VPS PHP dependencies
node_modules/ NO installed on VPS Node dependencies

10. Checklist: First Production Deploy

  • VPS: SSH key access configured
  • VPS: PHP 8.2+ selected in DirectAdmin MultiPHP
  • VPS: Node.js 20 installed via fnm
  • VPS: Composer installed globally
  • VPS: Production database created in DirectAdmin
  • VPS: Project directory created, public_html symlinked
  • VPS: Repository cloned from Gitea
  • VPS: .env configured with production values
  • VPS: php artisan key:generate run
  • VPS: deploy.sh path updated (APP_DIR)
  • VPS: First ./deploy.sh run successfully
  • VPS: Cron jobs added (scheduler + queue worker)
  • VPS: Permissions set on storage/ and bootstrap/cache/
  • VPS: SSL certificate active (Let's Encrypt via DirectAdmin)
  • VPS: Test the public URL in browser
  • VPS: Test login at /admin
  • VPS: Change the default superadmin password