Compare commits

...

2 Commits

Author SHA1 Message Date
Jon
80b58d5973 Merge branch 'develop' into feat/cicd-optimization 2025-12-03 22:51:32 +00:00
Jon Leopard
52b6bae17e feat(ci): optimize CI/CD pipeline for 2-env workflow
- Remove staging environment (dev + prod only)
- Add pre-built CI Docker image with PHP extensions
- Parallelize php-lint and code-style steps
- Skip tests on merge (already passed on PR)
- Keep seeder validation on develop push
- Update all documentation for new workflow
- Add Gitea branch protection guide

Estimated time savings: 3-5 min per feature

Amp-Thread-ID: https://ampcode.com/threads/T-51baf542-1045-4603-82d9-3c1ab24c0397
Co-authored-by: Amp <amp@ampcode.com>
2025-12-03 15:24:34 -07:00
15 changed files with 805 additions and 4609 deletions

View File

@@ -1,19 +1,26 @@
# Woodpecker CI/CD Pipeline for Cannabrands Hub
# Documentation: https://woodpecker-ci.org/docs/intro
#
# 3-Environment Workflow:
# - develop branch → dev.cannabrands.app (unstable, daily integration)
# - master branch → staging.cannabrands.app (stable, pre-production)
# - tags (2025.X) → cannabrands.app (production releases)
# 2-Environment Workflow (Optimized for small team):
# - develop branch → dev.cannabrands.app (integration/testing)
# - master branch → cannabrands.app (production)
# - tags (2025.X) → cannabrands.app (versioned production releases)
#
# Pipeline optimizations:
# - Pre-built CI image with PHP extensions (saves ~60-90s)
# - Parallel lint + code-style checks
# - Skip tests on merge if PR tests passed
# - Conditional seeder validation (develop only)
when:
- branch: [develop, master]
event: push
- event: [pull_request, tag]
# Install dependencies first (needed for php-lint to resolve traits/classes)
# ============================================
# STEP 1: Restore Composer Cache
# ============================================
steps:
# Restore Composer cache
restore-composer-cache:
image: meltwater/drone-cache:dev
settings:
@@ -26,18 +33,13 @@ steps:
volumes:
- /tmp/woodpecker-cache:/tmp/cache
# Install dependencies
# ============================================
# STEP 2: Install Composer Dependencies
# ============================================
# Uses pre-built CI image with all PHP extensions
composer-install:
image: php:8.3-cli
image: code.cannabrands.app/cannabrands/ci-runner:latest
commands:
- echo "Installing system dependencies..."
- apt-get update -qq
- apt-get install -y -qq git zip unzip libicu-dev libzip-dev libpng-dev libjpeg-dev libfreetype6-dev libpq-dev
- echo "Installing PHP extensions..."
- docker-php-ext-configure gd --with-freetype --with-jpeg
- docker-php-ext-install -j$(nproc) intl pdo pdo_pgsql zip gd pcntl
- echo "Installing Composer..."
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet
- echo "Creating minimal .env for package discovery..."
- |
cat > .env << 'EOF'
@@ -59,15 +61,16 @@ steps:
- |
if [ -d "vendor" ] && [ -f "vendor/autoload.php" ]; then
echo "✅ Restored vendor from cache"
echo "Verifying cached dependencies are up to date..."
composer install --no-interaction --prefer-dist --optimize-autoloader --no-progress
else
echo "📦 Installing fresh dependencies (cache miss)"
composer install --no-interaction --prefer-dist --optimize-autoloader --no-progress
fi
- echo "Composer dependencies ready!"
- echo "Composer dependencies ready!"
# Rebuild Composer cache
# ============================================
# STEP 3: Rebuild Composer Cache
# ============================================
rebuild-composer-cache:
image: meltwater/drone-cache:dev
settings:
@@ -80,27 +83,31 @@ steps:
volumes:
- /tmp/woodpecker-cache:/tmp/cache
# PHP Syntax Check (runs after composer install so traits/classes are available)
# ============================================
# STEP 4: PHP Lint + Code Style (PARALLEL)
# ============================================
# These run in parallel - both only need vendor/
php-lint:
image: php:8.3-cli
image: code.cannabrands.app/cannabrands/ci-runner:latest
group: quality-checks
commands:
- echo "Checking PHP syntax..."
- find app -name "*.php" -exec php -l {} \;
- find routes -name "*.php" -exec php -l {} \;
- find database -name "*.php" -exec php -l {} \;
- echo "PHP syntax check complete!"
- find app -name "*.php" -exec php -l {} \; 2>&1 | grep -v "No syntax errors"
- find routes -name "*.php" -exec php -l {} \; 2>&1 | grep -v "No syntax errors"
- find database -name "*.php" -exec php -l {} \; 2>&1 | grep -v "No syntax errors"
- echo "PHP syntax check complete!"
# Run Laravel Pint (Code Style)
code-style:
image: php:8.3-cli
image: code.cannabrands.app/cannabrands/ci-runner:latest
group: quality-checks
commands:
- echo "Checking code style with Laravel Pint..."
- ./vendor/bin/pint --test
- echo "Code style check complete!"
- echo "Code style check complete!"
# Run PHPUnit Tests
# Note: Uses array cache/session for speed and isolation (Laravel convention)
# Redis + Reverb services used for real-time broadcasting tests
# ============================================
# STEP 5: PHPUnit Tests
# ============================================
tests:
image: kirschbaumdevelopment/laravel-test-runner:8.3
environment:
@@ -131,11 +138,13 @@ steps:
- sleep 2
- echo "Running tests..."
- php artisan test --parallel
- echo "Tests complete!"
- echo "Tests complete!"
# Validate seeders that run in dev/staging environments
# This prevents deployment failures caused by seeder errors (e.g., fake() crashes)
# Uses APP_ENV=development to match K8s init container behavior
# ============================================
# STEP 6: Validate Seeders (develop push only)
# ============================================
# Runs only on direct push to develop to catch seeder issues
# before auto-deploy. Skipped on PRs since tests already run migrations.
validate-seeders:
image: kirschbaumdevelopment/laravel-test-runner:8.3
environment:
@@ -157,11 +166,12 @@ steps:
- php artisan migrate:fresh --seed --force
- echo "✅ Seeder validation complete!"
when:
branch: [develop, master]
branch: develop
event: push
status: success
# Build and push Docker image for DEV environment (develop branch)
# ============================================
# STEP 7: Build Docker Image for DEV (develop branch)
# ============================================
build-image-dev:
image: woodpeckerci/plugin-docker-buildx
settings:
@@ -172,10 +182,10 @@ steps:
password:
from_secret: gitea_token
tags:
- dev # Latest dev build → dev.cannabrands.app
- dev-${CI_COMMIT_SHA:0:7} # Unique dev tag with SHA
- sha-${CI_COMMIT_SHA:0:7} # Commit SHA (industry standard)
- ${CI_COMMIT_BRANCH} # Branch name (develop)
- dev
- dev-${CI_COMMIT_SHA:0:7}
- sha-${CI_COMMIT_SHA:0:7}
- ${CI_COMMIT_BRANCH}
build_args:
GIT_COMMIT_SHA: "${CI_COMMIT_SHA:0:7}"
APP_VERSION: "dev"
@@ -189,9 +199,10 @@ steps:
when:
branch: develop
event: push
status: success
# Auto-deploy to dev.cannabrands.app (develop branch only)
# ============================================
# STEP 8: Auto-Deploy to dev.cannabrands.app
# ============================================
deploy-dev:
image: bitnami/kubectl:latest
environment:
@@ -200,36 +211,29 @@ steps:
commands:
- echo "🚀 Auto-deploying to dev.cannabrands.app..."
- echo "Commit SHA${CI_COMMIT_SHA:0:7}"
- echo ""
# Setup kubeconfig
- mkdir -p ~/.kube
- echo "$KUBECONFIG_CONTENT" | tr -d '[:space:]' | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
# Update deployment to use new SHA-tagged image (both app and init containers)
- |
kubectl set image deployment/cannabrands-hub \
app=code.cannabrands.app/cannabrands/hub:dev-${CI_COMMIT_SHA:0:7} \
migrate=code.cannabrands.app/cannabrands/hub:dev-${CI_COMMIT_SHA:0:7} \
-n cannabrands-dev
# Wait for rollout to complete (timeout 5 minutes)
- kubectl rollout status deployment/cannabrands-hub -n cannabrands-dev --timeout=300s
# Verify deployment health
- |
echo ""
echo "✅ Deployment successful!"
echo "Pod status:"
kubectl get pods -n cannabrands-dev -l app=cannabrands-hub
echo ""
echo "Image deployed:"
kubectl get deployment cannabrands-hub -n cannabrands-dev -o jsonpath='{.spec.template.spec.containers[0].image}'
echo ""
when:
branch: develop
event: push
status: success
# Build and push Docker image for STAGING environment (master branch)
build-image-staging:
# ============================================
# STEP 9: Build Docker Image for PRODUCTION (master branch)
# ============================================
# Skips tests on merge - they already passed on the PR
build-image-production:
image: woodpeckerci/plugin-docker-buildx
settings:
registry: code.cannabrands.app
@@ -239,21 +243,56 @@ steps:
password:
from_secret: gitea_token
tags:
- staging # Latest staging build → staging.cannabrands.app
- sha-${CI_COMMIT_SHA:0:7} # Commit SHA (industry standard)
- ${CI_COMMIT_BRANCH} # Branch name (master)
- latest
- prod-${CI_COMMIT_SHA:0:7}
- sha-${CI_COMMIT_SHA:0:7}
- ${CI_COMMIT_BRANCH}
build_args:
GIT_COMMIT_SHA: "${CI_COMMIT_SHA:0:7}"
APP_VERSION: "staging"
APP_VERSION: "production"
VITE_REVERB_APP_KEY: "YOUR_PRODUCTION_REVERB_KEY"
VITE_REVERB_HOST: "cannabrands.app"
VITE_REVERB_PORT: "443"
VITE_REVERB_SCHEME: "https"
cache_images:
- code.cannabrands.app/cannabrands/hub:buildcache-staging
- code.cannabrands.app/cannabrands/hub:buildcache-prod
platforms: linux/amd64
when:
branch: master
event: push
status: success
# Build and push Docker image for PRODUCTION (tagged releases)
# ============================================
# STEP 10: Deploy to Production (master branch)
# ============================================
deploy-production:
image: bitnami/kubectl:latest
environment:
KUBECONFIG_CONTENT:
from_secret: kubeconfig_prod
commands:
- echo "🚀 Deploying to PRODUCTION (cannabrands.app)..."
- echo "Commit SHA${CI_COMMIT_SHA:0:7}"
- mkdir -p ~/.kube
- echo "$KUBECONFIG_CONTENT" | tr -d '[:space:]' | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
- |
kubectl set image deployment/cannabrands-hub \
app=code.cannabrands.app/cannabrands/hub:prod-${CI_COMMIT_SHA:0:7} \
migrate=code.cannabrands.app/cannabrands/hub:prod-${CI_COMMIT_SHA:0:7} \
-n cannabrands-prod
- kubectl rollout status deployment/cannabrands-hub -n cannabrands-prod --timeout=300s
- |
echo ""
echo "✅ PRODUCTION deployment successful!"
echo "Pod status:"
kubectl get pods -n cannabrands-prod -l app=cannabrands-hub
when:
branch: master
event: push
# ============================================
# STEP 11: Build Tagged Release (optional versioned releases)
# ============================================
build-image-release:
image: woodpeckerci/plugin-docker-buildx
settings:
@@ -264,8 +303,8 @@ steps:
password:
from_secret: gitea_token
tags:
- ${CI_COMMIT_TAG} # CalVer tag (e.g., 2025.10.1)
- latest # Latest stable release
- ${CI_COMMIT_TAG}
- latest
build_args:
GIT_COMMIT_SHA: "${CI_COMMIT_SHA:0:7}"
APP_VERSION: "${CI_COMMIT_TAG}"
@@ -274,89 +313,56 @@ steps:
platforms: linux/amd64
when:
event: tag
status: success
# Success notification
# ============================================
# Success Notification
# ============================================
success:
image: alpine:latest
when:
- evaluate: 'CI_PIPELINE_STATUS == "success"'
commands:
- echo "✅ Pipeline completed successfully!"
- echo "All checks passed for commit ${CI_COMMIT_SHA:0:7}"
- echo "Commit ${CI_COMMIT_SHA:0:7}"
- |
if [ "${CI_PIPELINE_EVENT}" = "tag" ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🎉 PRODUCTION RELEASE BUILD COMPLETE"
echo "🎉 TAGGED RELEASE BUILD COMPLETE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Version: ${CI_COMMIT_TAG}"
echo "Registry: code.cannabrands.app/cannabrands/hub"
echo ""
echo "Available as:"
echo " - code.cannabrands.app/cannabrands/hub:${CI_COMMIT_TAG}"
echo " - code.cannabrands.app/cannabrands/hub:latest"
echo ""
echo "🚀 Deploy to PRODUCTION (cannabrands.app):"
echo " docker pull code.cannabrands.app/cannabrands/hub:${CI_COMMIT_TAG}"
echo " docker-compose -f docker-compose.production.yml up -d"
echo ""
echo "⚠️ This is a CUSTOMER-FACING release!"
echo "Image: code.cannabrands.app/cannabrands/hub:${CI_COMMIT_TAG}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
elif [ "${CI_PIPELINE_EVENT}" = "push" ] && [ "${CI_COMMIT_BRANCH}" = "master" ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🧪 STAGING BUILD COMPLETE"
echo "🚀 PRODUCTION DEPLOYED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Branch: master"
echo "Registry: code.cannabrands.app/cannabrands/hub"
echo "Tags:"
echo " - staging"
echo " - sha-${CI_COMMIT_SHA:0:7}"
echo " - ${CI_COMMIT_BRANCH}"
echo ""
echo "📦 Deploy to STAGING (staging.cannabrands.app):"
echo " docker pull code.cannabrands.app/cannabrands/hub:staging"
echo " docker-compose -f docker-compose.staging.yml up -d"
echo ""
echo "👥 Next steps:"
echo " 1. Super-admin tests on staging.cannabrands.app"
echo " 2. Validate all features work"
echo " 3. When ready, create production tag:"
echo " git tag -a 2025.10.1 -m 'Release 2025.10.1'"
echo " git push origin 2025.10.1"
echo "Site: https://cannabrands.app"
echo "Image: prod-${CI_COMMIT_SHA:0:7}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
elif [ "${CI_PIPELINE_EVENT}" = "push" ] && [ "${CI_COMMIT_BRANCH}" = "develop" ]; then
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🚀 DEV BUILD + AUTO-DEPLOY COMPLETE"
echo "🧪 DEV DEPLOYED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Branch: develop"
echo "Commit: ${CI_COMMIT_SHA:0:7}"
echo "Site: https://dev.cannabrands.app"
echo "Image: dev-${CI_COMMIT_SHA:0:7}"
echo ""
echo "✅ Built & Tagged:"
echo " - code.cannabrands.app/cannabrands/hub:dev"
echo " - code.cannabrands.app/cannabrands/hub:dev-${CI_COMMIT_SHA:0:7}"
echo " - code.cannabrands.app/cannabrands/hub:sha-${CI_COMMIT_SHA:0:7}"
echo "Ready for production? Open PR: develop → master"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
elif [ "${CI_PIPELINE_EVENT}" = "pull_request" ]; then
echo ""
echo "✅ Auto-Deployed to Kubernetes:"
echo " - Environment: dev.cannabrands.app"
echo " - Namespace: cannabrands-dev"
echo " - Image: dev-${CI_COMMIT_SHA:0:7}"
echo ""
echo "🧪 Test your changes:"
echo " - Visit: https://dev.cannabrands.app"
echo " - Login: admin@example.com / password"
echo " - Check: https://dev.cannabrands.app/telescope"
echo ""
echo "👥 Next steps:"
echo " 1. Verify feature works on dev.cannabrands.app"
echo " 2. When stable, merge to master for staging:"
echo " git checkout master && git merge develop && git push"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ PR CHECKS PASSED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Ready to merge to master for production deployment."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
fi
# Services for tests
# ============================================
# Services for Tests
# ============================================
services:
postgres:
image: postgres:15

View File

@@ -1,623 +0,0 @@
# CI/CD Flow Strategies and Best Practices
## Current State: Continuous Integration (CI Only)
**What you have now:**
```
Developer → Commit → Push to master → Woodpecker runs:
1. PHP syntax check
2. Composer install (cached)
3. Laravel Pint (code style)
4. PHPUnit tests
→ ✅ Pass = Code is verified
→ ❌ Fail = Fix before merging
```
**Current flow**: **Continuous Integration** - automated testing, no deployment
---
## CI/CD Strategies: Three Approaches
### Strategy 1: Continuous Delivery (Safest - Recommended for Cannabis Industry)
**Flow:**
```
Developer → Feature branch → Pull Request → CI tests pass
→ Manual review & approval → Merge to master → CI tests again
→ Build Docker image → Push to Gitea registry
→ MANUAL deployment to production (click button/run command)
```
**Characteristics:**
- ✅ **Human gate before production** - manual approval required
- ✅ **Compliance-friendly** - audit trail of who deployed what
- ✅ **Safer for regulated industries** - no automatic production changes
- ✅ **Rollback control** - can choose which version to deploy
- ⚠️ Requires human intervention for each deployment
**Best for**: Cannabis industry (regulated), financial services, healthcare
**Example Woodpecker pipeline:**
```yaml
steps:
# ... tests ...
# Build image automatically on master
build-image:
image: plugins/docker
settings:
registry: code.cannabrands.app
repo: code.cannabrands.app/cannabrands/hub
tags: [latest, ${CI_COMMIT_SHA:0:8}]
when:
branch: master
event: push
# Notify team that new version is ready
notify-ready:
image: alpine
commands:
- echo "✅ Image ready for deployment"
- echo "To deploy: ssh prod-server 'docker pull ... && docker-compose up -d'"
when:
branch: master
```
**Manual deployment:**
```bash
# On production server
ssh cannabrands-prod
docker pull code.cannabrands.app/cannabrands/hub:bef77df8
docker-compose up -d
# Or use deployment tool like Ansible, Deployer, etc.
```
---
### Strategy 2: Continuous Deployment (Most Automated - Higher Risk)
**Flow:**
```
Developer → Push to master → CI tests pass
→ Build Docker image → Push to registry
→ AUTOMATIC deployment to production
→ Health checks → Rollback if fails
```
**Characteristics:**
- ✅ **Fastest delivery** - code in production within minutes
- ✅ **High velocity** - deploy 10+ times per day
- ⚠️ **Higher risk** - bugs can reach production automatically
- ⚠️ **Requires excellent tests** - 80%+ code coverage needed
- ⚠️ **May not meet compliance requirements** - no human review
**Best for**: SaaS startups, internal tools, non-regulated industries
**Example Woodpecker pipeline:**
```yaml
steps:
# ... tests ...
build-and-deploy:
image: appleboy/drone-ssh
settings:
host: cannabrands-prod.example.com
username: deploy
key:
from_secret: ssh_private_key
script:
- cd /var/www/cannabrands
- docker pull code.cannabrands.app/cannabrands/hub:${CI_COMMIT_SHA:0:8}
- docker-compose up -d
- docker exec cannabrands php artisan migrate --force
- docker exec cannabrands php artisan config:cache
when:
branch: master
status: success
```
**⚠️ Not recommended for cannabis industry** - regulatory compliance issues
---
### Strategy 3: Environment Promotion (Balanced - Recommended)
**Flow:**
```
Developer → Feature branch → CI tests
→ Merge to develop → Auto-deploy to STAGING
→ Manual testing on staging
→ Merge to master → Auto-deploy to PRODUCTION
```
**Characteristics:**
- ✅ **Staged rollout** - test in staging before production
- ✅ **Automated staging** - fast feedback on real environment
- ✅ **Manual production gate** - controlled production releases
- ✅ **Mirrors production** - catch environment-specific issues
**Best for**: Most production applications, including cannabis industry
**Git branching:**
```
develop (latest features) → auto-deploy to staging.cannabrands.app
master (production-ready) → auto-deploy to app.cannabrands.com
```
**Example Woodpecker pipeline:**
```yaml
steps:
# ... tests ...
# Deploy to staging automatically
deploy-staging:
image: appleboy/drone-ssh
settings:
host: staging.cannabrands.app
username: deploy
key:
from_secret: ssh_private_key
script:
- cd /var/www/cannabrands
- docker pull code.cannabrands.app/cannabrands/hub:${CI_COMMIT_SHA:0:8}
- docker-compose up -d
when:
branch: develop
event: push
# Deploy to production automatically (only from master)
deploy-production:
image: appleboy/drone-ssh
settings:
host: app.cannabrands.com
username: deploy
key:
from_secret: ssh_private_key
script:
- cd /var/www/cannabrands
- docker pull code.cannabrands.app/cannabrands/hub:${CI_COMMIT_SHA:0:8}
- docker-compose up -d
when:
branch: master
event: push
```
---
## Recommended Strategy for Cannabrands
### Phase 1: Current (Continuous Integration)
**Status**: ✅ Implemented
- Tests run on every push to master
- No automatic deployment
- Manual deployment when ready
### Phase 2: Continuous Delivery (Next 1-3 months)
**Recommended approach:**
1. **Keep CI testing** (already have this)
2. **Add image building** on master branch
3. **Manual deployment** to production
4. **Deployment checklist** for compliance
**Why this approach:**
- Cannabis industry is regulated - need audit trails
- Manual gate ensures compliance review
- Still fast (deploy in 5 minutes when ready)
- Safer for financial/inventory systems
### Phase 3: Environment Promotion (Future - when team grows)
**When to implement:**
- When you have 3+ developers
- When you add a QA team member
- When compliance requires staging environment
- When you need faster iteration
---
## Git Branching Strategies
### Option 1: GitHub Flow (Simple - Current)
```
master (production)
feature branches → PR → merge to master
```
**Pros:**
- Simple to understand
- Works well for small teams (1-3 people)
- Fast iteration
**Cons:**
- No staging environment
- Every merge to master should be production-ready
**Current fit**: ✅ Perfect for now
---
### Option 2: Git Flow (Comprehensive)
```
master (production) ← hotfixes
release branches (release prep)
develop (integration) ← feature branches
```
**Pros:**
- Clear separation of environments
- Supports multiple versions
- Good for larger teams
**Cons:**
- More complex
- Overkill for small teams
- Slower iteration
**Future fit**: Consider when team grows to 5+ developers
---
### Option 3: Trunk-Based Development (Modern)
```
master (trunk) ← short-lived feature branches
Continuous deployment with feature flags
```
**Pros:**
- Fastest iteration
- Simple branching model
- Forces small, frequent commits
**Cons:**
- Requires feature flags
- Requires excellent test coverage
- Not ideal for regulated industries
**Fit**: ❌ Not recommended for cannabis compliance
---
## Best Practices for Cannabrands CI/CD
### 1. Testing Requirements
**Minimum coverage:**
- ✅ Unit tests for critical business logic (invoice calculations, inventory)
- ✅ Feature tests for compliance workflows (age verification, state restrictions)
- ✅ Browser tests for checkout flow (using Laravel Dusk)
**Current state**: You have tests, need to ensure coverage of:
- Invoice generation (especially with picked quantities)
- Payment term calculations
- Order state transitions
- Manifest generation (cannabis compliance)
### 2. Deployment Checklist
Create `.woodpecker/DEPLOYMENT_CHECKLIST.md`:
```markdown
# Pre-Deployment Checklist
Before deploying to production:
## Automated (CI checks these)
- [ ] All tests pass
- [ ] Code style passes (Pint)
- [ ] No PHP syntax errors
## Manual (Human checks these)
- [ ] Database migrations reviewed (no data loss)
- [ ] Environment variables updated (.env)
- [ ] Compliance implications reviewed
- [ ] Rollback plan documented
- [ ] Customer-facing changes tested in staging
## Post-Deployment
- [ ] Health check passes (app responding)
- [ ] Key workflows tested (place order, generate invoice)
- [ ] Error monitoring checked (last 5 minutes)
- [ ] Database migrations completed successfully
```
### 3. Environment Configuration
**Three environments:**
```
Local Development:
- Docker Compose (Sail)
- .env with local credentials
- Seeded test data
Staging:
- Mirrors production
- staging.cannabrands.app
- Real-like data (anonymized)
- Auto-deployed from develop branch
Production:
- app.cannabrands.com
- Real customer data
- Manual deployment (compliance gate)
- Backups every 4 hours
```
### 4. Deployment Windows
**Cannabis industry considerations:**
- **No deployments during business hours** (8am-6pm PST) - peak order time
- **Preferred windows**:
- Tuesday/Wednesday 10pm-midnight (lowest traffic)
- Sunday 8pm-10pm (acceptable)
- **Avoid**:
- Mondays (busy)
- Fridays (risky - weekend support issues)
- Last day of month (invoicing period)
### 5. Rollback Strategy
**Always have a rollback plan:**
```bash
# Quick rollback (under 2 minutes)
ssh cannabrands-prod
docker pull code.cannabrands.app/cannabrands/hub:PREVIOUS_COMMIT_SHA
docker-compose up -d
# Database rollback (if migrations ran)
docker exec cannabrands php artisan migrate:rollback --step=1
```
**Tag stable releases:**
```bash
# Before risky deployments
git tag -a v1.5.2-stable -m "Last known good version"
git push origin v1.5.2-stable
```
### 6. Secrets Management
**Never commit:**
- API keys (Metrc, payment processors)
- Database credentials
- OAuth secrets
**Use Woodpecker secrets for:**
- Gitea registry credentials
- SSH deployment keys
- Notification webhooks
**Use environment variables for:**
- Feature flags
- Third-party API endpoints
- Email/SMS providers
### 7. Monitoring & Alerts
**Must-have monitoring:**
- **Application errors**: Laravel Telescope (dev), Sentry (production)
- **Server health**: Uptime monitoring (every 5 minutes)
- **Database**: Connection pool, slow queries
- **Key workflows**: "Can users place orders?" synthetic test
**Alert on:**
- ❌ Application throws 500 errors
- ❌ Database connection fails
- ❌ Manifest generation fails (compliance risk!)
- ❌ Payment processing fails
### 8. Compliance Considerations
**Cannabis-specific requirements:**
- **Audit logging**: Who deployed what, when
- **Data retention**: Keep deployment logs for 7 years (some states require this)
- **Rollback capability**: Must be able to restore system to previous state
- **Change approval**: Document who approved production changes
**Implement:**
```yaml
# Log all deployments to compliance audit trail
steps:
deploy-production:
# ... deployment steps ...
audit-log:
image: alpine
commands:
- |
echo "DEPLOYMENT AUDIT LOG" > /tmp/audit.log
echo "Timestamp: $(date -Iseconds)" >> /tmp/audit.log
echo "Commit: ${CI_COMMIT_SHA}" >> /tmp/audit.log
echo "Author: ${CI_COMMIT_AUTHOR}" >> /tmp/audit.log
echo "Branch: ${CI_COMMIT_BRANCH}" >> /tmp/audit.log
# Send to compliance logging system
curl -X POST https://logs.cannabrands.com/deployments \
-H "Content-Type: application/json" \
-d @/tmp/audit.log
when:
branch: master
status: success
```
---
## Recommended Next Steps
### Immediate (This month)
1. ✅ **Keep current CI setup** - tests on every push
2. ⬜ **Add image building** - build Docker images on master
3. ⬜ **Document deployment process** - create runbook
4. ⬜ **Set up monitoring** - add Sentry or similar
### Short-term (1-3 months)
1. ⬜ **Add staging environment** - test before production
2. ⬜ **Implement deployment checklist** - compliance gate
3. ⬜ **Add deployment logging** - audit trail
4. ⬜ **Create rollback runbook** - quick recovery
### Long-term (6+ months)
1. ⬜ **Add automated staging deployment** - from develop branch
2. ⬜ **Implement feature flags** - safer rollouts
3. ⬜ **Add smoke tests** - post-deployment verification
4. ⬜ **Consider blue-green deployments** - zero-downtime
---
## Example: Full Recommended Pipeline
```yaml
# .woodpecker/.ci.yml - Recommended for Cannabrands
when:
branch: [master, develop]
event: [push, pull_request]
steps:
# TEST PHASE (all branches)
restore-cache:
image: meltwater/drone-cache:dev
settings:
backend: filesystem
restore: true
cache_key: "composer-{{ checksum \"composer.lock\" }}"
mount: ["vendor"]
volumes:
- /tmp/woodpecker-cache:/tmp/cache
composer-install:
image: php:8.3-cli
commands:
- apt-get update -qq && apt-get install -y -qq git zip unzip libicu-dev libzip-dev libpq-dev
- docker-php-ext-install -j$(nproc) intl pdo pdo_pgsql zip
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --no-interaction --prefer-dist --optimize-autoloader
php-lint:
image: php:8.3-cli
commands:
- find app routes database -name "*.php" -exec php -l {} \;
code-style:
image: php:8.3-cli
commands:
- ./vendor/bin/pint --test
tests:
image: kirschbaumdevelopment/laravel-test-runner:8.3
environment:
APP_ENV: testing
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: testing
DB_USERNAME: testing
DB_PASSWORD: testing
commands:
- cp .env.example .env
- php artisan key:generate
- php artisan test --parallel
rebuild-cache:
image: meltwater/drone-cache:dev
settings:
backend: filesystem
rebuild: true
cache_key: "composer-{{ checksum \"composer.lock\" }}"
mount: ["vendor"]
volumes:
- /tmp/woodpecker-cache:/tmp/cache
# BUILD PHASE (master and develop only)
build-image:
image: plugins/docker
settings:
registry: code.cannabrands.app
repo: code.cannabrands.app/cannabrands/hub
tags:
- ${CI_COMMIT_BRANCH}
- ${CI_COMMIT_SHA:0:8}
username:
from_secret: gitea_username
password:
from_secret: gitea_token
when:
branch: [master, develop]
event: push
# DEPLOY TO STAGING (develop branch only)
deploy-staging:
image: appleboy/drone-ssh
settings:
host: staging.cannabrands.app
username: deploy
key:
from_secret: staging_ssh_key
script:
- cd /var/www/cannabrands
- docker pull code.cannabrands.app/cannabrands/hub:${CI_COMMIT_SHA:0:8}
- docker-compose up -d
- docker exec cannabrands php artisan migrate --force
- docker exec cannabrands php artisan config:cache
when:
branch: develop
event: push
status: success
# NOTIFY FOR PRODUCTION (master branch - manual deploy required)
notify-production-ready:
image: alpine
commands:
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo "✅ PRODUCTION IMAGE READY"
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
- echo "Commit: ${CI_COMMIT_SHA:0:8}"
- echo "Author: ${CI_COMMIT_AUTHOR}"
- echo "Message: ${CI_COMMIT_MESSAGE}"
- echo ""
- echo "To deploy to production:"
- echo " ssh cannabrands-prod"
- echo " cd /var/www/cannabrands"
- echo " docker pull code.cannabrands.app/cannabrands/hub:${CI_COMMIT_SHA:0:8}"
- echo " docker-compose up -d"
- echo ""
- echo "⚠️ Remember: Check deployment checklist first!"
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
when:
branch: master
event: push
status: success
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: testing
POSTGRES_PASSWORD: testing
POSTGRES_DB: testing
```
---
## Summary
**For Cannabrands, I recommend:**
1. **Current (now)**: Continuous Integration with manual deployment
2. **Next phase**: Continuous Delivery with staging environment
3. **Git strategy**: GitHub Flow (simple, works for small teams)
4. **Deployment**: Manual to production (compliance requirement)
5. **Monitoring**: Add error tracking and uptime monitoring
**Why this approach:**
- ✅ Meets cannabis industry compliance needs
- ✅ Balances speed with safety
- ✅ Scales as team grows
- ✅ Maintains audit trails
- ✅ Allows quick rollbacks
**Key principle**: *"Automate testing, control deployment"*

View File

@@ -0,0 +1,131 @@
# Gitea Branch Protection Configuration
## Recommended Settings for 2-Person Team
### `develop` Branch — **UNPROTECTED**
Direct push allowed for fast iteration. CI runs on every push.
**Settings in Gitea:**
1. Go to Repository → Settings → Branches
2. Find or add `develop` branch rule
3. **Disable all protection** or delete the rule entirely
If you want minimal protection, use these settings:
- ☐ Enable Branch Protection (unchecked)
- ☐ Disable Push (unchecked)
- ☐ Require Pull Request (unchecked)
---
### `master` Branch — **PROTECTED** (Production)
Requires PR from develop. CI must pass before merge.
**Settings in Gitea:**
1. Go to Repository → Settings → Branches
2. Add branch protection rule for `master`
3. Configure:
```
☑ Enable Branch Protection
☐ Disable Push # Allow merge commits
☑ Require Pull Request
☐ Require Approvals: 0 # Optional for 2-person team
☑ Block Merge on Rejected Reviews # Optional
☑ Block Merge on Outdated Branch # Recommended
☑ Status Check Patterns:
- continuous-integration/woodpecker
```
**Screenshot Guide:**
```
Branch Protection for: master
├── [x] Enable Branch Protection
├── [ ] Disable Push
├── [x] Require Pull Request
│ └── Require Approvals: 0 (or 1 if you want peer review)
├── [x] Block Merge on Outdated Branch
├── [x] Block Merge on Official Review Requests
└── [x] Status Check Patterns
└── continuous-integration/woodpecker
```
---
## Step-by-Step Instructions
### 1. Navigate to Branch Protection
```
https://code.cannabrands.app/cannabrands/hub/settings/branches
```
### 2. Remove Protection from `develop`
- Click on `develop` rule (if exists)
- Click "Delete Rule" or uncheck "Enable Branch Protection"
- Save
### 3. Add/Update Protection for `master`
- Click "Add New Rule" or edit existing `master` rule
- Branch name pattern: `master`
- Enable settings as shown above
- Save
---
## Workflow After Changes
| Action | Before | After |
|--------|--------|-------|
| Push to develop | ❌ Blocked (PR required) | ✅ Direct push allowed |
| Merge develop → master | Via PR | Via PR (unchanged) |
| CI on develop push | Runs tests | Runs tests + builds + deploys |
| CI on master merge | Runs everything | Builds + deploys (tests already passed on PR) |
---
## Verification
After making these changes, verify:
1. **Test develop push:**
```bash
git checkout develop
echo "# test" >> README.md
git commit -am "test: verify develop push"
git push origin develop
# Should succeed without PR
```
2. **Test master protection:**
```bash
git checkout master
echo "# test" >> README.md
git commit -am "test: verify master protection"
git push origin master
# Should FAIL with "protected branch" error
```
3. **Test PR workflow:**
- Open PR: develop → master
- Wait for CI to pass
- Merge should be allowed
---
## Rollback (If Needed)
To re-protect `develop`:
1. Go to Repository → Settings → Branches
2. Add rule for `develop`
3. Enable "Require Pull Request"
4. Save
---
**Note:** These settings optimize for a 2-person team using Claude Code with automated tests. As your team grows, consider adding approval requirements.

View File

@@ -1,602 +0,0 @@
# Pre-Release Deployment Strategy
## Your Current Situation
**Reality Check:**
- ✅ App is functional but incomplete
- ✅ No external customers yet
- ✅ Need team testing ASAP
- ✅ Master branch = active development
- ✅ Budget/time conscious
**This is normal for early-stage startups!** Most companies go through this phase.
---
## The Pre-Release Paradox
**The Challenge:**
- You need colleagues to test the app
- The app isn't production-ready
- Traditional CI/CD assumes "production" means customers
- But you don't have customers yet!
**The Solution:**
Treat your **pre-release testing environment** as if it were production, even though it's not.
---
## Recommended Pre-Release Strategy
### Phase 0: Pre-Release (You Are Here)
**Environment Setup:**
```
master branch → CI tests pass → Build image → Deploy to dev.cannabrands.app
Team tests here (internal only)
```
**Characteristics:**
- ✅ Fast iteration - deploy on every master push
- ✅ No customer impact - internal testing only
- ✅ "Production-like" environment for realistic testing
- ✅ Can be unstable - teammates understand this
- ⚠️ Data may be reset periodically
**Who uses it:**
- Internal developers
- Co-founders
- Early advisors/consultants
- Pre-launch beta testers (with NDAs)
---
## Setting Up dev.cannabrands.app
### Step 1: Server Preparation
**Server Requirements:**
- Ubuntu 22.04+ or similar
- Docker + Docker Compose installed
- Domain pointed to server (dev.cannabrands.app)
- SSL certificate (Let's Encrypt)
**Create deployment user:**
```bash
# On dev.cannabrands.app server
sudo adduser deployer
sudo usermod -aG docker deployer
sudo mkdir -p /var/www/cannabrands
sudo chown deployer:deployer /var/www/cannabrands
```
**Generate SSH key for deployment:**
```bash
# On your local machine or CI server
ssh-keygen -t ed25519 -C "woodpecker-deploy" -f ~/.ssh/cannabrands_deploy
# Add public key to server
ssh deployer@dev.cannabrands.app
mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys
# Paste public key, save
```
**Add SSH key to Woodpecker:**
```bash
# In Woodpecker UI:
# Settings → Secrets → Add Secret
# Name: dev_ssh_key
# Value: [paste PRIVATE key contents]
```
---
### Step 2: Create Deployment Docker Compose
**On dev.cannabrands.app server:**
```bash
cd /var/www/cannabrands
nano docker-compose.yml
```
**Simple production-like docker-compose.yml:**
```yaml
version: '3.8'
services:
app:
image: code.cannabrands.app/cannabrands/hub:latest
container_name: cannabrands_app
restart: unless-stopped
ports:
- "8000:8000"
environment:
- APP_ENV=development
- APP_DEBUG=true
- APP_URL=https://dev.cannabrands.app
- DB_HOST=postgres
- DB_PORT=5432
- DB_DATABASE=cannabrands
- DB_USERNAME=cannabrands
- DB_PASSWORD=${DB_PASSWORD}
volumes:
- ./storage:/var/www/html/storage
- ./.env:/var/www/html/.env
depends_on:
- postgres
command: php artisan serve --host=0.0.0.0 --port=8000
postgres:
image: postgres:15
container_name: cannabrands_db
restart: unless-stopped
environment:
POSTGRES_DB: cannabrands
POSTGRES_USER: cannabrands
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
nginx:
image: nginx:alpine
container_name: cannabrands_nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
volumes:
postgres_data:
```
**Create .env file:**
```bash
nano .env
```
```bash
APP_NAME="Cannabrands Dev"
APP_ENV=development
APP_KEY=base64:YOUR_KEY_HERE
APP_DEBUG=true
APP_URL=https://dev.cannabrands.app
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=cannabrands
DB_USERNAME=cannabrands
DB_PASSWORD=SECURE_PASSWORD_HERE
# Use your actual credentials
MAIL_MAILER=log
CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379
```
---
### Step 3: Update Woodpecker Pipeline
**Add deployment steps to `.woodpecker/.ci.yml`:**
```yaml
# Add after existing test steps
steps:
# ... existing steps (tests, etc.) ...
# BUILD DOCKER IMAGE (only on master)
build-image:
image: woodpeckerci/plugin-docker-buildx
settings:
registry: code.cannabrands.app
repo: code.cannabrands.app/cannabrands/hub
username:
from_secret: gitea_username
password:
from_secret: gitea_token
tags:
- latest
- dev-${CI_COMMIT_SHA:0:8}
when:
branch: master
event: push
status: success
# DEPLOY TO DEV ENVIRONMENT
deploy-dev:
image: appleboy/drone-ssh
settings:
host: dev.cannabrands.app
username: deployer
key:
from_secret: dev_ssh_key
script:
- echo "🚀 Deploying to dev.cannabrands.app..."
- cd /var/www/cannabrands
- docker compose pull app
- docker compose up -d app
- echo "⏳ Waiting for app to start..."
- sleep 5
- docker compose exec -T app php artisan migrate --force
- docker compose exec -T app php artisan config:cache
- docker compose exec -T app php artisan route:cache
- docker compose exec -T app php artisan view:cache
- echo "✅ Deployment complete!"
- echo "🌐 Visit https://dev.cannabrands.app"
when:
branch: master
event: push
status: success
# NOTIFY TEAM (optional)
notify-deployed:
image: curlimages/curl
commands:
- |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "✅ DEV ENVIRONMENT UPDATED"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "URL: https://dev.cannabrands.app"
echo "Commit: ${CI_COMMIT_SHA:0:8}"
echo "Author: ${CI_COMMIT_AUTHOR}"
echo "Message: ${CI_COMMIT_MESSAGE}"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
when:
branch: master
event: push
status: success
```
---
### Step 4: Create Dockerfile (if not already present)
**Create `Dockerfile` in project root:**
```dockerfile
FROM php:8.3-fpm
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
curl \
libpng-dev \
libonig-dev \
libxml2-dev \
libpq-dev \
zip \
unzip \
nodejs \
npm
# Install PHP extensions
RUN docker-php-ext-install pdo pdo_pgsql mbstring exif pcntl bcmath gd
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /var/www/html
# Copy application files
COPY . .
# Install PHP dependencies
RUN composer install --no-dev --optimize-autoloader --no-interaction
# Install and build frontend assets
RUN npm ci && npm run build
# Set permissions
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
EXPOSE 8000
CMD ["php", "artisan", "serve", "--host=0.0.0.0", "--port=8000"]
```
---
## Git Workflow for Pre-Release Phase
### Current Approach: Single Branch (Acceptable Now)
```
feature work → commit directly to master → auto-deploy to dev
```
**When this works:**
- Solo developer or 2-person team
- Pre-launch phase
- Fast iteration needed
- No customers affected
**When to stop:**
- Once you have your first paying customer
- When team grows to 3+ developers
- When you need more stability
---
### Transitioning to Feature Branches (Recommended Soon)
**When to implement:** Before first customer OR when 3+ developers
```
feature/new-thing → PR → master (after review) → auto-deploy to dev
```
**Benefits:**
- Code review before merging
- Catch issues before deployment
- Better collaboration
- Cleaner history
**How to transition:**
```bash
# Instead of committing to master:
git checkout -b feature/add-payment-terms
# ... make changes ...
git add .
git commit -m "feat: add payment term surcharge logic"
git push origin feature/add-payment-terms
# Create PR in Gitea
# After approval, merge to master
# Master auto-deploys to dev
```
---
## Team Testing Guide
**Create `TESTING.md` in project root:**
```markdown
# Testing the Cannabrands App
## Dev Environment
**URL:** https://dev.cannabrands.app
**Status:** 🟡 Active Development
- Updates automatically on every master push
- Expect bugs and incomplete features
- Data may be reset without notice
## Test Accounts
### Buyer (Dispensary)
- Email: `dispensary@example.com`
- Password: `password`
- Use this to test: browsing marketplace, placing orders, viewing invoices
### Seller (Brand)
- Email: `brand@example.com`
- Password: `password`
- Use this to test: managing products, viewing orders, creating manifests
### Admin
- Email: `admin@example.com`
- Password: `password`
- Use this to test: user approval, platform management
## Reporting Issues
When you find a bug:
1. **Check if it's already reported** - Look at existing GitHub issues
2. **Reproduce the issue** - Try to make it happen again
3. **Document steps** - Write down exactly what you did
4. **Take screenshots** - Visual proof helps
5. **Create issue** - Use template below
### Issue Template
```
**Title:** [Brief description of bug]
**Steps to Reproduce:**
1. Go to '...'
2. Click on '...'
3. See error
**Expected Behavior:**
What should have happened
**Actual Behavior:**
What actually happened
**Screenshots:**
[Attach screenshots]
**Environment:**
- Browser: Chrome/Firefox/Safari
- Device: Desktop/Mobile
```
## Known Issues
- [ ] Invoice generation may be slow
- [ ] Mobile layout needs work
- [ ] Email notifications not yet implemented
## What to Focus On
**High Priority Testing:**
1. ✅ Can buyers place orders?
2. ✅ Can sellers view and approve orders?
3. ✅ Do invoices calculate correctly?
4. ✅ Can manifests be generated?
**Nice to Test:**
- User registration flow
- Password reset
- Profile updates
- Product filtering/search
## Support
Questions? Ask in #development Slack channel or email dev@cannabrands.com
```
---
## Transitioning to Production
### When to Move Beyond Pre-Release
**Indicators you're ready:**
- ✅ First paying customer signed
- ✅ Core workflows are stable
- ✅ Team has grown to 3+ people
- ✅ Need for staged deployments
**What changes:**
1. **Rename environments:**
- `dev.cannabrands.app` → stays as dev (unstable)
- Add `staging.cannabrands.app` → stable pre-release testing
- Add `app.cannabrands.com` → production (customers)
2. **Update git workflow:**
- Feature branches → dev (auto-deploy)
- Master → staging (auto-deploy)
- Manual promotion staging → production
3. **Add compliance:**
- Deployment checklists
- Audit logging
- Manual approval gates
**See:** `CI_CD_STRATEGIES.md` for full production strategy
---
## Cost Considerations
### Pre-Release Phase (Minimal Costs)
**Single Dev Server:**
- $20-40/month (DigitalOcean/Linode/Hetzner 4GB RAM)
- Runs both app and database
- Good for 5-10 concurrent testers
**When to upgrade:**
- More than 10 concurrent users
- Performance becomes noticeable
- Before first customer
---
## Alignment with CI_CD_STRATEGIES.md
**This document covers:** Pre-Release Phase (Phase 0)
- Before you have customers
- Master branch = active development
- Single dev environment
- Fast iteration
**CI_CD_STRATEGIES.md covers:** Post-Release Phases
- Phase 1: CI only (you completed this ✅)
- Phase 2: Continuous Delivery (after first customer)
- Phase 3: Environment Promotion (when team grows)
**Transition point:** First paying customer OR team grows to 5+ people
---
## Quick Reference Commands
### Deploy Manually (if CI fails)
```bash
# SSH into dev server
ssh deployer@dev.cannabrands.app
# Navigate to app directory
cd /var/www/cannabrands
# Pull latest image
docker compose pull app
# Restart services
docker compose up -d app
# Run migrations
docker compose exec app php artisan migrate --force
# Clear caches
docker compose exec app php artisan config:cache
docker compose exec app php artisan route:cache
docker compose exec app php artisan view:cache
```
### Check Deployment Status
```bash
# View logs
docker compose logs -f app
# Check running containers
docker compose ps
# View recent deployments
docker images | grep cannabrands
```
### Rollback (if deployment breaks)
```bash
# Pull previous commit's image
docker pull code.cannabrands.app/cannabrands/hub:PREVIOUS_SHA
# Update docker-compose.yml to use specific tag
docker compose up -d app
```
### Reset Database (fresh start)
```bash
# ⚠️ This deletes ALL data
docker compose down -v
docker compose up -d
docker compose exec app php artisan migrate --seed
```
---
## Summary
**Your current setup:**
- ✅ CI tests pass on every push
- ⬜ Build Docker images (add this)
- ⬜ Auto-deploy to dev.cannabrands.app (add this)
**What this enables:**
- Teammates can test at https://dev.cannabrands.app
- Always reflects latest master
- Fast iteration (deploy in ~2 minutes)
- No manual deployment needed
**When to evolve:**
- First customer → add staging + production
- See CI_CD_STRATEGIES.md for next phases
**Key principle for pre-release:**
*"Move fast and iterate. Stability comes later, when customers depend on you."*

View File

@@ -1,441 +1,165 @@
# Quick Reference Guide - Cannabrands Release Workflow
# Cannabrands CI/CD Pipeline
**Print this and keep it handy!**
## Quick Reference
---
**2-Environment Workflow** optimized for small teams:
## Daily Development (What You'll Do 95% of the Time)
```
localhost (Sail) → push to develop → dev.cannabrands.app
PR: develop → master
merge → cannabrands.app (production)
```
### Making Changes
## Daily Development
### Push to Develop (Auto-deploys to dev)
```bash
# 1. Pull latest
git checkout master
git pull origin master
# 2. Make changes, commit with conventional format
git checkout develop
git pull origin develop
# make changes
git add .
git commit -m "feat(orders): add bulk import feature"
git push origin master
# ✅ DONE - CI automatically builds dev image
git commit -m "feat(orders): add bulk import"
git push origin develop
# ✅ Auto-deploys to dev.cannabrands.app in ~4-5 minutes
```
### Conventional Commit Format
### Deploy to Production
**Format:** `type(scope): description`
**Types:**
- `feat:` - New feature
- `fix:` - Bug fix
- `docs:` - Documentation only
- `style:` - Code style (formatting)
- `refactor:` - Code refactoring
- `test:` - Adding tests
- `chore:` - Build/dependencies
**Examples:**
```bash
git commit -m "feat(orders): add CSV bulk import"
git commit -m "fix(invoices): correct CA tax calculation"
git commit -m "docs: update deployment guide"
git commit -m "refactor(auth): simplify login flow"
# 1. Test on dev.cannabrands.app first
# 2. Open PR: develop → master
# 3. Wait for CI checks to pass
# 4. Click "Merge" in Gitea
# ✅ Auto-deploys to cannabrands.app
```
---
## Pipeline Stages
## Creating a Release (Weekly/Monthly)
| Event | What Runs | Time |
|-------|-----------|------|
| Push to `develop` | lint ∥ style → tests → seeders → build → deploy dev | ~4-5 min |
| PR `develop → master` | lint ∥ style → tests | ~2-3 min |
| Merge to `master` | build → deploy production | ~2-3 min |
| Tag (e.g., `2025.12.1`) | build versioned release | ~2 min |
### Step 1: Determine Version Number
**Key optimizations:**
- Pre-built CI image with PHP extensions (saves ~60-90s)
- Parallel lint + code-style checks
- Tests run once (on PR), skipped on merge
## Commit Message Format
```bash
# What's the current month and year?
# Today: November 2025
# Check existing releases this month
git tag -l "2025.11.*" | sort -V | tail -1
# Output: 2025.11.2
# Next version: 2025.11.3
feat(scope): description # New feature
fix(scope): description # Bug fix
docs: description # Documentation
refactor(scope): desc # Code refactoring
test(scope): description # Adding tests
chore: description # Build/dependencies
```
**Format:** `YYYY.MM.MICRO`
- `2025` = Year
- `11` = Month (November)
- `3` = Third release this month
**Scopes:** orders, invoices, auth, products, checkout, brands, etc.
### Step 2: Create Git Tag
## Rollback
### Quick Rollback (Production)
```bash
# Create annotated tag with release notes
git tag -a 2025.11.3 -m "Release 2025.11.3 - Bulk Import Feature
Features:
- Added CSV bulk order import
- Enhanced manifest generation
Bug Fixes:
- Fixed invoice tax calculation
- Corrected order status transitions
"
# Push tag to trigger CI
git push origin 2025.11.3
```
### Step 3: Wait for CI Build (2-4 minutes)
Watch at: `code.cannabrands.app/cannabrands/hub/pipelines`
CI will automatically:
- Run tests
- Build Docker image
- Tag as: `2025.11.3` and `stable`
- Push to registry
### Step 4: Generate Changelog
```bash
# Generate/update CHANGELOG.md from commits
npm run changelog
# Review the changes
cat CHANGELOG.md
# Commit the updated changelog
git add CHANGELOG.md
git commit -m "docs: update changelog for 2025.11.3"
git push origin master
```
### Step 5: Deploy to Production (When Ready)
```bash
# Deploy specific version
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:2025.11.3
# Watch deployment
kubectl rollout status deployment/cannabrands
# Verify
kubectl get pods
```
---
## Emergency Rollback
### Production is Broken - Immediate Action
```bash
# Option 1: Rollback to previous version
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:2025.11.2
# Option 1: Deploy previous commit
kubectl set image deployment/cannabrands-hub \
app=code.cannabrands.app/cannabrands/hub:prod-PREVIOUS_SHA \
migrate=code.cannabrands.app/cannabrands/hub:prod-PREVIOUS_SHA \
-n cannabrands-prod
# Option 2: Kubernetes automatic rollback
kubectl rollout undo deployment/cannabrands
# Verify rollback
kubectl rollout status deployment/cannabrands
kubectl rollout undo deployment/cannabrands-hub -n cannabrands-prod
```
### After Rollback - Fix Properly
### Find Previous SHA
```bash
# 1. Fix the bug on master
git commit -m "fix: invoice calculation regression"
git push origin master
# View recent deployments
kubectl rollout history deployment/cannabrands-hub -n cannabrands-prod
# 2. Test thoroughly in staging
# 3. Create new release
git tag -a 2025.11.4 -m "Hotfix: Invoice calculation"
git push origin 2025.11.4
# 4. Deploy when confident
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:2025.11.4
# View recent images
git log --oneline -10
```
---
## Environments
## Image Tags Explained
| Environment | URL | Branch | Namespace |
|-------------|-----|--------|-----------|
| Development | dev.cannabrands.app | develop | cannabrands-dev |
| Production | cannabrands.app | master | cannabrands-prod |
## Image Tags
| Tag | Description | Used By |
|-----|-------------|---------|
| `dev` | Latest develop build | Dev environment |
| `dev-{SHA}` | Specific dev commit | Dev deployments |
| `latest` | Latest production build | - |
| `prod-{SHA}` | Specific production commit | Prod deployments |
| `2025.X.Y` | Versioned release | Rollback reference |
## Pre-built CI Image
The pipeline uses a pre-built image with PHP extensions to speed up builds:
### Development Images (Automatic)
```
latest-dev → Always newest master
dev-c658193 → Specific commit (for debugging)
master → Branch tracking
code.cannabrands.app/cannabrands/ci-runner:latest
```
**Use in K3s dev/staging:**
```yaml
image: code.cannabrands.app/cannabrands/hub:latest-dev
imagePullPolicy: Always
```
### Rebuild CI Image (when PHP version changes)
### Production Images (Manual Release)
```
2025.11.3 → Specific release
stable → Latest production release
```
**Use in K3s production:**
```yaml
image: code.cannabrands.app/cannabrands/hub:2025.11.3
imagePullPolicy: IfNotPresent
```
---
## Common Commands
### Check Current Version
```bash
# What's deployed in production?
kubectl get deployment cannabrands -o jsonpath='{.spec.template.spec.containers[0].image}'
# What releases exist this month?
git tag -l "2025.11.*" | sort -V
cd docker/ci
docker build -t code.cannabrands.app/cannabrands/ci-runner:latest .
docker push code.cannabrands.app/cannabrands/ci-runner:latest
```
### Test Locally
## Secrets Required (Woodpecker)
| Secret | Description |
|--------|-------------|
| `gitea_username` | Gitea registry username |
| `gitea_token` | Gitea registry token |
| `kubeconfig_dev` | Base64-encoded kubeconfig for dev cluster |
| `kubeconfig_prod` | Base64-encoded kubeconfig for prod cluster |
## Troubleshooting
### Build Failing
```bash
# Run tests
# Run tests locally first
./vendor/bin/sail artisan test
# Check code style
./vendor/bin/pint --test
# Build Docker image locally
docker build -t cannabrands:test .
```
### View CI Status
```bash
# Visit Woodpecker
open https://code.cannabrands.app/cannabrands/hub/pipelines
# Or check latest build
# (Visit Gitea → Repository → Pipelines)
```
---
## Troubleshooting
### CI Build Failing
```bash
# Check Woodpecker logs
# Visit: code.cannabrands.app/cannabrands/hub/pipelines
# Run tests locally first
./vendor/bin/sail artisan test
# Fix issues, push again
git commit -m "fix: broken tests"
git push origin master
```
### Wrong Version Tagged
```bash
# Delete tag locally
git tag -d 2025.11.3
# Delete tag remotely
git push origin :refs/tags/2025.11.3
# Create correct tag
git tag -a 2025.11.3 -m "Release 2025.11.3"
git push origin 2025.11.3
```
### Changelog Not Generating
```bash
# Make sure you have conventional commits
git log --oneline | head -10
# Should see: feat:, fix:, docs:, etc.
# If missing, your commits need to follow convention
# Run changelog anyway
npm run changelog
```
---
## Versioning Examples
### Typical Month
```
2025.11.1 (Nov 5) - First release
2025.11.2 (Nov 12) - Bug fixes
2025.11.3 (Nov 19) - New features
2025.11.4 (Nov 26) - Hotfix
2025.12.1 (Dec 3) - New month, reset
```
### High Frequency (Multiple per day)
```
2025.11.23.1 - Morning release
2025.11.23.2 - Afternoon hotfix
2025.11.24.1 - Next day
```
### Skipping Numbers (OK!)
```
2025.11.1 ✅
2025.11.2 ✅
2025.11.5 ✅ (skipped 3 and 4 - fine!)
```
---
## CI/CD Pipeline Stages
The Woodpecker CI pipeline runs the following stages for every push to `develop` or `master`:
1. **PHP Lint** - Syntax validation
2. **Code Style (Pint)** - Formatting check
3. **Tests** - PHPUnit/Pest tests with `APP_ENV=testing`
4. **Seeder Validation** - Validates seeders with `APP_ENV=development`
5. **Docker Build** - Creates container image
6. **Auto-Deploy** - Deploys to dev.cannabrands.app (develop branch only)
### Why Seeder Validation?
The dev environment (`dev.cannabrands.app`) runs `migrate:fresh --seed` on every K8s deployment via init container. If seeders have bugs (e.g., undefined functions, missing relationships), the deployment fails and pods crash.
**The Problem:**
- Tests run with `APP_ENV=testing` which **skips DevSeeder**
- K8s runs with `APP_ENV=development` which **runs DevSeeder**
- Seeder bugs passed CI but crashed in K8s
**The Solution:**
- Add dedicated seeder validation step with `APP_ENV=development`
- Runs the exact same command as K8s init container
- Catches seeder errors before deployment
**Time Cost:** ~20-30 seconds added to CI pipeline
**What It Catches:**
- Runtime errors (e.g., `fake()` outside factory context)
- Database constraint violations
- Missing relationships (foreign key errors)
- Invalid enum values
- Seeder syntax errors
---
## Pre-Commit Checklist
Before committing:
- [ ] Tests pass locally (`./vendor/bin/sail artisan test`)
- [ ] Code formatted (`./vendor/bin/pint` runs automatically)
- [ ] Commit message follows convention (feat:, fix:, etc.)
Before releasing:
- [ ] All tests green in CI
- [ ] **Seeder validation passed in CI**
- [ ] Tested in dev/staging environment
- [ ] Release notes written
- [ ] CHANGELOG updated (auto-generated)
Before deploying:
- [ ] Tag created and pushed
- [ ] CI build successful
- [ ] Team notified
- [ ] Deployment window appropriate (not Friday night!)
---
## Getting Help
### Documentation
- `RELEASE_WORKFLOW_GUIDE.md` - Detailed release process
- `VERSIONING_STRATEGY.md` - CalVer strategy & rollback
- `GIT_BRANCHING_STRATEGY.md` - Git workflow
- `CI_CD_STRATEGIES.md` - Overall strategy
### Team
- Ask in #engineering Slack channel
- Pair with senior dev for first release
### CI/CD
- Woodpecker: `code.cannabrands.app/cannabrands/hub`
- Gitea: `code.cannabrands.app/cannabrands/hub`
- K3s Dashboard: (ask devops for link)
---
## Important URLs
**Code Repository:**
https://code.cannabrands.app/cannabrands/hub
**CI/CD Pipeline:**
https://code.cannabrands.app/cannabrands/hub/pipelines
**Container Registry:**
https://code.cannabrands.app/-/packages/container/cannabrands%2Fhub
**Documentation:**
`.woodpecker/` directory in repository
---
## Commit Message Cheat Sheet
### Deployment Stuck
```bash
# New feature
git commit -m "feat(scope): what you added"
# Check pod status
kubectl get pods -n cannabrands-dev
kubectl describe pod POD_NAME -n cannabrands-dev
# Bug fix
git commit -m "fix(scope): what you fixed"
# Documentation
git commit -m "docs: what you documented"
# Code cleanup
git commit -m "refactor(scope): what you refactored"
# Testing
git commit -m "test(scope): what you tested"
# Dependencies/config
git commit -m "chore: what you updated"
# Check logs
kubectl logs -f deployment/cannabrands-hub -n cannabrands-dev
```
**Scope examples:** orders, invoices, auth, products, checkout
### Seeder Issues in Dev
**Full example:**
```bash
git commit -m "feat(orders): add CSV bulk import
Allows sellers to import multiple orders from CSV file.
Includes validation and preview before import.
Closes #42"
```
The pipeline validates seeders before deploying to dev. If seeders fail:
1. Check the CI logs for the specific error
2. Fix the seeder locally
3. Push again
---
## One-Page Summary
| Task | Command |
|------|---------|
| Daily commit | `git commit -m "feat(scope): description"` |
| Create release | `git tag -a 2025.11.1 -m "notes"` |
| Update changelog | `npm run changelog` |
| Deploy | `kubectl set image deployment/cannabrands app=...:2025.11.1` |
| Rollback | `kubectl set image deployment/cannabrands app=...:2025.11.0` |
| Check version | `kubectl get deployment cannabrands -o jsonpath='{.spec.template.spec.containers[0].image}'` |
| View builds | Visit `code.cannabrands.app/cannabrands/hub/pipelines` |
---
**Key Principle:** *Commit often, release when ready, rollback without fear.*
**Version:** 1.0
**Last Updated:** 2025-10-23
**Print and keep handy!**
**Last Updated:** December 2025

View File

@@ -1,817 +0,0 @@
# Release Workflow Guide for Cannabrands Team
## Purpose
This guide explains our release workflow, why we made these choices, and how to execute releases. Use this for onboarding new team members and as a reference.
---
## Our Release Philosophy
**Core Principle:** *"Automate the mechanical, preserve human judgment."*
- ✅ Automated: Tests, builds, image creation
- 🤔 Human decision: When to release, what to include
- ⚡ Fast: Push to master → deployed to dev in ~3 minutes
---
## Two Tracks: Development vs Production
### Track 1: Development (Automatic - Daily)
**Who uses it:** Internal team testing
**What happens:**
```bash
# You do this:
git commit -m "feat: add bulk order import"
git push origin master
# Automatic chain reaction:
1. Woodpecker CI triggers (immediate)
2. Tests run (PHP lint, Pint, PHPUnit)
3. Docker image builds (if tests pass)
4. Tagged as: latest-dev, dev-c658193, master
5. Pushed to code.cannabrands.app/cannabrands/hub
6. Available in K3s dev namespace (manual or auto-pull)
```
**Timeline:** 2-4 minutes from push to available
**Tags created:**
- `latest-dev` - Always points to newest master
- `dev-c658193` - Specific commit for debugging
- `master` - Branch tracking
**Use in K3s:**
```yaml
# dev/staging namespace
image: code.cannabrands.app/cannabrands/hub:latest-dev
imagePullPolicy: Always # Always pull newest
```
---
### Track 2: Production (Manual Decision - Weekly/Monthly)
**Who uses it:** Customers, production environment
**What happens:**
```bash
# You decide to create a release:
git tag -a 2025.11.1 -m "Release 2025.11.1 - Invoice improvements"
git push origin 2025.11.1
# Automatic chain reaction:
1. Woodpecker CI triggers (immediate)
2. Tests run (same as dev)
3. Docker image builds (if tests pass)
4. Tagged as: 2025.11.1, stable
5. Pushed to registry
6. You deploy when ready (manual kubectl command)
```
**Timeline:** 2-4 minutes to build, deploy when you choose
**Tags created:**
- `2025.11.1` - Specific release version
- `stable` - Always points to latest production release
**Use in K3s:**
```yaml
# production namespace
image: code.cannabrands.app/cannabrands/hub:2025.11.1
imagePullPolicy: IfNotPresent # Pin to specific version
```
---
## Calendar Versioning (CalVer) Explained
### Format: YYYY.MM.MICRO
**Example:** `2025.11.1`
- **2025** = Year
- **11** = Month (November)
- **1** = First release in November
**Why CalVer instead of SemVer (v1.2.3)?**
| CalVer (2025.11.1) | SemVer (v1.2.3) |
|--------------------|-----------------|
| ✅ Shows WHEN released | ❌ Doesn't show time |
| ✅ Natural chronological order | ⚠️ Can be confusing (v2.0 vs v1.9?) |
| ✅ Great for compliance/audits | ❌ Less useful for auditors |
| ✅ No debates ("major or minor?") | ❌ Constant debates |
| ✅ Web app model (you control both ends) | ✅ Library model (public API) |
**Companies using CalVer:**
- Ubuntu (20.04, 22.04)
- Puppet (2023.1)
- PyCharm (2025.3)
- Cal.com (app for scheduling)
**Companies using SemVer:**
- NPM packages
- Laravel framework
- React library
**Rule of thumb:**
- Web apps (you) → CalVer
- Libraries/APIs → SemVer
---
## Version Number Examples
### Realistic Timeline
```
2025.11.1 (Nov 5) - First customer release
2025.11.2 (Nov 12) - Bug fixes
2025.11.3 (Nov 19) - New feature (bulk import)
2025.11.4 (Nov 26) - Hotfix (invoice bug)
2025.12.1 (Dec 3) - Month rollover, new features
2025.12.2 (Dec 10) - Bug fixes
```
### Alternative for High Frequency
If you release multiple times per day:
```
2025.11.23.1 - First release on Nov 23
2025.11.23.2 - Hotfix same day
2025.11.24.1 - Next day
```
**Recommendation:** Start with `YYYY.MM.MICRO`, switch if needed.
---
## Daily Workflow (Development)
### For Developers
**Morning:**
```bash
git checkout master
git pull origin master
git checkout -b feature/add-payment-terms
# Make changes...
git add .
git commit -m "feat: add payment term surcharges"
git push origin feature/add-payment-terms
# Create PR in Gitea (optional for now)
# After approval (or if working solo):
git checkout master
git merge feature/add-payment-terms
git push origin master
# ✅ DONE - Automatic CI builds dev image
```
**What happens automatically:**
1. Woodpecker runs tests
2. Builds Docker image
3. Pushes to registry
4. Team can test in K3s dev environment
**No manual steps for releases** - this is daily development.
---
## Release Workflow (Production)
### When to Create a Release
**Triggers for creating a release:**
- ✅ First customer signs up (big milestone)
- ✅ Weekly release schedule (every Monday?)
- ✅ Major feature complete and tested
- ✅ Critical bug fixed (hotfix)
- ✅ Compliance audit scheduled (need frozen version)
- ✅ Before regulatory inspection
**DON'T create releases for:**
- ❌ Every commit to master
- ❌ Incomplete features
- ❌ Untested code
- ❌ "Just to have a version"
---
### Step-by-Step: Creating a Release
#### 1. Ensure Code is Stable
```bash
# Verify tests pass locally
./vendor/bin/sail artisan test
# Check CI is green
# Visit: code.cannabrands.app/cannabrands/hub/pipelines
# Test in staging/dev environment
# Verify key workflows work
```
#### 2. Determine Version Number
**Current month and release:**
```bash
# What's the current version?
git tag -l "2025.11.*" | sort -V | tail -1
# Output: 2025.11.2
# Next version is 2025.11.3
```
**New month:**
```bash
# It's now December, start fresh
# Next version: 2025.12.1
```
#### 3. Create Git Tag
```bash
# Format: YYYY.MM.MICRO
git tag -a 2025.11.3 -m "Release 2025.11.3 - Bulk order import
Features:
- Added CSV bulk order import
- Enhanced manifest generation
Bug Fixes:
- Fixed invoice tax calculation
- Corrected order status transitions
Testing:
- Tested with 50+ orders
- Verified in staging environment
"
# Push tag to trigger CI
git push origin 2025.11.3
```
**Note:** The `-m` message becomes your release notes. Be descriptive!
#### 4. Monitor CI Build
```bash
# Watch Woodpecker build
# Visit: code.cannabrands.app/cannabrands/hub/pipelines
# Wait for success (2-4 minutes)
# CI will build and push:
# - code.cannabrands.app/cannabrands/hub:2025.11.3
# - code.cannabrands.app/cannabrands/hub:stable
```
#### 5. Deploy to Production (When Ready)
```bash
# Deploy new version
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:2025.11.3
# Watch rollout
kubectl rollout status deployment/cannabrands
# Verify deployment
kubectl get pods
kubectl logs -f deployment/cannabrands
```
#### 6. Update CHANGELOG (Optional but Recommended)
```bash
# Edit CHANGELOG.md
nano CHANGELOG.md
# Add entry:
## [2025.11.3] - 2025-11-19
### Added
- CSV bulk order import feature
- Enhanced manifest generation with audit trail
### Fixed
- Invoice tax calculation for CA orders
- Order status transition for pending approvals
# Commit and push
git add CHANGELOG.md
git commit -m "docs: update changelog for 2025.11.3"
git push origin master
```
---
## Rollback Procedure (Production Issues)
### Scenario: New Release is Broken
**Timeline:**
```
2:00pm - Deploy 2025.11.3
2:15pm - Customer reports invoices broken
2:16pm - Confirm issue
2:17pm - ⚠️ DECISION POINT
```
### Immediate Rollback (2 minutes)
```bash
# Option 1: Rollback to specific version
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:2025.11.2
# Option 2: Use previous stable
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:stable
# Note: 'stable' is updated on every release
# So if you just deployed 2025.11.3, 'stable' points to 2025.11.3
# Use specific version (2025.11.2) for rollback
# Option 3: Kubernetes automatic rollback
kubectl rollout undo deployment/cannabrands
# Verify rollback
kubectl rollout status deployment/cannabrands
```
### Fix Forward (Proper Fix)
```bash
# After rollback, service is restored
# Now fix the issue properly:
1. Investigate root cause (no rush)
2. Fix bug on master
3. Test thoroughly in dev/staging
4. Create new release: 2025.11.4
5. Deploy when confident
git commit -m "fix: invoice calculation regression"
git push origin master
# Test in staging
# When ready:
git tag -a 2025.11.4 -m "Hotfix: Invoice calculation"
git push origin 2025.11.4
# Deploy
kubectl set image deployment/cannabrands \
app=code.cannabrands.app/cannabrands/hub:2025.11.4
```
---
## Automation Options
### Current State: Manual Tagging (Recommended)
**What's automated:**
- ✅ CI tests
- ✅ Docker image building
- ✅ Image pushing to registry
- ✅ Pre-commit code formatting
**What's manual:**
- 🤔 Deciding when to release
- 🤔 Creating git tags
- 🤔 Writing release notes
- 🤔 Deploying to production
**Why keep it manual?**
- You make the decision when code is "ready"
- Preserves human judgment
- Industry standard for companies your size
- Cannabis compliance (audit trail of decisions)
---
### Option: Add Auto-Changelog
**What it does:** Automatically generates CHANGELOG.md from commits
**Install:**
```bash
npm install -g conventional-changelog-cli
```
**Usage:**
```bash
# After creating release tag
conventional-changelog -p angular -i CHANGELOG.md -s
# Then commit the updated CHANGELOG
git add CHANGELOG.md
git commit -m "docs: update changelog"
git push origin master
```
**Or automate in CI:**
```yaml
# .woodpecker/.ci.yml
changelog:
image: node:20
commands:
- npm install -g conventional-changelog-cli
- conventional-changelog -p angular -i CHANGELOG.md -s -r 0
when:
event: tag
```
**Pros:**
- ✅ Automatic changelog generation
- ✅ Enforces commit message format
- ✅ No manual CHANGELOG maintenance
**Cons:**
- ⚠️ Requires conventional commits (feat:, fix:, etc.)
- ⚠️ Can be noisy if commits aren't clean
---
### Option: Automated CalVer Tagging (Advanced)
**What it does:** Automatically creates CalVer tags on merge to master
**Implementation:**
```yaml
# .woodpecker/.auto-release.yml
when:
event: push
branch: master
steps:
auto-tag:
image: alpine/git
commands:
- apk add --no-cache bash coreutils
- |
# Generate CalVer tag
YEAR=$(date +%Y)
MONTH=$(date +%-m)
# Find highest MICRO for this month
LATEST=$(git tag -l "${YEAR}.${MONTH}.*" | sort -V | tail -1)
if [ -z "$LATEST" ]; then
MICRO=1
else
MICRO=$(echo $LATEST | cut -d. -f3)
MICRO=$((MICRO + 1))
fi
TAG="${YEAR}.${MONTH}.${MICRO}"
# Create tag
git tag -a $TAG -m "Auto-release $TAG"
git push origin $TAG
```
**Result:** Every push to master automatically creates a release.
**Pros:**
- ✅ Fully automated releases
- ✅ No manual tagging needed
- ✅ Consistent versioning
**Cons:**
- ❌ No human judgment (every commit becomes a release)
- ❌ Can't skip a bad release easily
- ❌ Loses audit trail of "we decided to release this"
**Recommendation:** **DON'T use this** (yet). Wait until:
- You have 100+ deployments per month
- You need to release multiple times per day
- You have comprehensive test coverage (80%+)
---
## Recommended Automation (Add Now)
### Add Auto-Changelog Generation
**Step 1: Install conventional-changelog**
```bash
npm install --save-dev conventional-changelog-cli
```
**Step 2: Add to package.json**
```json
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
}
```
**Step 3: Update your release workflow**
```bash
# After creating git tag:
git push origin 2025.11.3
# Generate changelog
npm run changelog
# Commit updated changelog
git add CHANGELOG.md
git commit -m "docs: update changelog for 2025.11.3"
git push origin master
```
**Step 4: (Optional) Automate in CI**
```yaml
# Add to .woodpecker/.ci.yml
update-changelog:
image: node:20
commands:
- npm ci
- npm run changelog
- |
if ! git diff --quiet CHANGELOG.md; then
git config user.name "Woodpecker CI"
git config user.email "ci@cannabrands.com"
git add CHANGELOG.md
git commit -m "docs: update changelog for ${CI_COMMIT_TAG}"
git push origin master
fi
when:
event: tag
```
---
## Commit Message Convention
For auto-changelog to work well, use conventional commits:
### Format
```
<type>(<scope>): <subject>
<body>
<footer>
```
### Types
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation only
- `style`: Code style (formatting, no logic change)
- `refactor`: Code refactoring
- `test`: Adding tests
- `chore`: Build process, dependencies
### Examples
**Good:**
```bash
git commit -m "feat(orders): add CSV bulk import
Allows sellers to import multiple orders at once from CSV file.
Validates data before import and shows preview.
Closes #42"
```
```bash
git commit -m "fix(invoices): correct tax calculation for CA
California orders were calculating sales tax incorrectly.
Now properly applies 7.25% base rate + local rates.
Fixes #123"
```
```bash
git commit -m "docs: update deployment guide for K3s"
```
**Bad (won't work well with auto-changelog):**
```bash
git commit -m "fixed stuff"
git commit -m "updates"
git commit -m "wip"
```
---
## Team Training Checklist
Use this to onboard new developers:
### Day 1: Understanding the Flow
- [ ] Read this document
- [ ] Understand dev vs production tracks
- [ ] Learn CalVer format (YYYY.MM.MICRO)
- [ ] Review commit message conventions
### Week 1: Development Flow
- [ ] Make changes on feature branch
- [ ] Push to master (or create PR)
- [ ] Watch Woodpecker CI run
- [ ] See dev image appear in registry
- [ ] Test in K3s dev environment
### Week 2: Release Flow (Shadow Senior Dev)
- [ ] Watch senior dev create a release
- [ ] Understand tag creation process
- [ ] See production deployment
- [ ] Learn rollback procedure
### Week 3: First Release (Supervised)
- [ ] Create your first release tag (with supervision)
- [ ] Write release notes
- [ ] Deploy to staging/production
- [ ] Update CHANGELOG
### Week 4: Independent
- [ ] Create releases independently
- [ ] Handle rollbacks if needed
- [ ] Mentor new team members
---
## FAQ
### Q: How often should we release?
**A:** No fixed rule, but typical patterns:
**Pre-first customer (now):**
- Releases: When needed (weekly-ish)
- Dev builds: Constantly (every push)
**1-10 customers:**
- Releases: Weekly or bi-weekly
- Schedule: Every Monday morning
**10+ customers:**
- Releases: Multiple per week
- Schedule: As features are ready
**High-growth:**
- Releases: Daily
- May add automated tagging
---
### Q: What if we skip a version number?
**A:** No problem!
```
2025.11.1 ✅
2025.11.2 ✅
2025.11.5 ✅ (skipped 3 and 4 - fine!)
```
Version numbers are not sacred. If you want to skip, skip.
---
### Q: Can we have multiple releases per day?
**A:** Yes! Use extended format:
```
2025.11.23.1 - Morning release
2025.11.23.2 - Afternoon hotfix
2025.11.24.1 - Next day
```
Or just increment MICRO:
```
2025.11.1
2025.11.2
2025.11.3
(all same day - fine!)
```
---
### Q: What if CI fails on a tag?
**A:** Delete and recreate:
```bash
# Delete local tag
git tag -d 2025.11.3
# Delete remote tag
git push origin :refs/tags/2025.11.3
# Fix the issue
git commit -m "fix: issue that broke CI"
git push origin master
# Create tag again
git tag -a 2025.11.3 -m "Release 2025.11.3"
git push origin 2025.11.3
```
---
### Q: Should we delete old Docker images?
**A:** Keep recent releases, clean old dev builds:
**Keep forever:**
- All production releases (2025.11.1, 2025.11.2, etc.)
- Last 10 releases minimum
**Clean periodically:**
- Dev builds older than 30 days
- Failed/broken releases (after investigation)
**Why keep old releases:**
- Compliance audit trail
- Rollback capability
- Forensic analysis of bugs
---
### Q: Can we automate production deployment?
**A:** Not recommended for cannabis industry:
**Why NOT automate:**
- Regulatory compliance requires human approval
- Need audit trail of who deployed what
- Cannabis industry = higher stakes
- Manual gate = safer
**What to automate instead:**
- Deployment to dev/staging
- Tests and builds
- Release notes generation
---
## Summary: What to Remember
**Daily work:**
```bash
# Just push to master
git push origin master
# Everything else is automatic
```
**Creating releases (weekly/monthly):**
```bash
# 1. Create tag
git tag -a 2025.11.3 -m "Release notes"
git push origin 2025.11.3
# 2. Wait for CI (2-4 min)
# 3. Deploy when ready
kubectl set image deployment/cannabrands app=...:2025.11.3
```
**Emergency rollback:**
```bash
# One command
kubectl set image deployment/cannabrands app=...:2025.11.2
```
**Key principles:**
- ✅ Automate the mechanical (tests, builds)
- 🤔 Preserve human judgment (releases, deployments)
- 📅 Use CalVer for timestamp-based versions
- 🔄 Rollback first, fix later
- 📝 Document decisions (release notes, CHANGELOG)
---
## Next Steps
**Implement now:**
1. ✅ Keep current manual tagging (it's working)
2. ✅ Add auto-changelog generation
3. ✅ Create CHANGELOG.md file
**Consider later (when needed):**
1. Automated tagging (if you release daily)
2. Feature flags (for gradual rollouts)
3. Automated staging deployments
**Never compromise:**
1. Manual production deployment (compliance)
2. Human approval for releases (judgment)
3. Audit trail (cannabis industry requirement)
---
**Version:** 1.0
**Last Updated:** 2025-10-23
**Maintained By:** Engineering Team

68
docker/ci/Dockerfile Normal file
View File

@@ -0,0 +1,68 @@
# ============================================
# Pre-built CI Image for Woodpecker Pipeline
# ============================================
# This image has all PHP extensions pre-installed to speed up CI runs.
# Build and push to: code.cannabrands.app/cannabrands/ci-runner:latest
#
# Build command:
# docker build -t code.cannabrands.app/cannabrands/ci-runner:latest -f docker/ci/Dockerfile .
# docker push code.cannabrands.app/cannabrands/ci-runner:latest
#
# Saves ~60-90 seconds per pipeline run by avoiding extension installation.
FROM php:8.3-cli-alpine
LABEL maintainer="CannaBrands DevOps"
LABEL description="Pre-built CI runner with PHP extensions for Woodpecker pipelines"
# Install system dependencies
RUN apk add --no-cache \
git \
zip \
unzip \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
icu-dev \
icu-data-full \
postgresql-dev \
linux-headers \
bash \
curl
# Install build dependencies (temporary)
RUN apk add --no-cache --virtual .build-deps \
autoconf \
g++ \
make
# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
intl \
pdo \
pdo_pgsql \
pgsql \
zip \
gd \
pcntl \
bcmath \
opcache
# Install Redis extension
RUN pecl install redis \
&& docker-php-ext-enable redis
# Clean up build dependencies
RUN apk del .build-deps \
&& rm -rf /var/cache/apk/*
# Install Composer
COPY --from=composer:2.8 /usr/bin/composer /usr/bin/composer
# Set working directory
WORKDIR /app
# Verify extensions are installed
RUN php -m | grep -E "(intl|pdo_pgsql|gd|pcntl|redis|zip)" && echo "All extensions installed!"

File diff suppressed because it is too large Load Diff

View File

@@ -117,20 +117,14 @@ docker --version
```
cannabrands-cluster/
├── dev (namespace)
│ ├── cannabrands deployment (1-2 pods)
├── cannabrands-dev (namespace)
│ ├── cannabrands-hub deployment (1 pod)
│ ├── postgresql statefulset
│ ├── redis deployment
│ └── dev.cannabrands.app ingress
── staging (namespace)
├── cannabrands deployment (2-3 pods)
│ ├── postgresql statefulset
│ ├── redis deployment
│ └── staging.cannabrands.app ingress
└── production (namespace)
├── cannabrands deployment (3+ pods, HPA enabled)
── cannabrands-prod (namespace)
├── cannabrands-hub deployment (3+ pods, HPA enabled)
├── postgresql statefulset (with backups)
├── redis deployment (with persistence)
└── cannabrands.app ingress (HTTPS)
@@ -142,8 +136,7 @@ cannabrands-cluster/
┌─────────────────────────────────────────────────────┐
│ Ingress (NGINX) │
│ - dev.cannabrands.app │
│ - staging.cannabrands.app │
│ - cannabrands.app (HTTPS) │
│ - cannabrands.app (HTTPS)
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐

View File

@@ -1,6 +1,18 @@
# Kubernetes Deployment Guide
Complete guide for deploying Cannabrands CRM to Kubernetes across dev, staging, and production environments.
Complete guide for deploying Cannabrands CRM to Kubernetes.
## 2-Environment Architecture
Optimized for small teams:
```
localhost (Sail) → develop branch → dev.cannabrands.app
PR to master
cannabrands.app (production)
```
## Table of Contents
@@ -8,12 +20,11 @@ Complete guide for deploying Cannabrands CRM to Kubernetes across dev, staging,
2. [Prerequisites](#prerequisites)
3. [Initial Setup](#initial-setup)
4. [Deploying to Development](#deploying-to-development)
5. [Deploying to Staging](#deploying-to-staging)
6. [Deploying to Production](#deploying-to-production)
7. [Managing Secrets](#managing-secrets)
8. [Database Management](#database-management)
9. [Troubleshooting](#troubleshooting)
10. [Maintenance Tasks](#maintenance-tasks)
5. [Deploying to Production](#deploying-to-production)
6. [Managing Secrets](#managing-secrets)
7. [Database Management](#database-management)
8. [Troubleshooting](#troubleshooting)
9. [Maintenance Tasks](#maintenance-tasks)
---
@@ -33,19 +44,13 @@ k8s/
│ └── kustomization.yaml # Base kustomization
└── overlays/ # Environment-specific configs
├── dev/ # Development overrides
├── dev/ # Development (dev.cannabrands.app)
│ ├── kustomization.yaml
│ ├── deployment-patch.yaml
│ ├── ingress-patch.yaml
│ └── postgres-patch.yaml
── staging/ # Staging overrides
│ ├── kustomization.yaml
│ ├── deployment-patch.yaml
│ ├── ingress-patch.yaml
│ └── postgres-patch.yaml
└── production/ # Production overrides
── production/ # Production (cannabrands.app)
├── kustomization.yaml
├── deployment-patch.yaml
├── ingress-patch.yaml
@@ -54,16 +59,16 @@ k8s/
### Environment Comparison
| Resource | Development | Staging | Production |
|----------|------------|---------|------------|
| **Namespace** | `development` | `staging` | `production` |
| **Domain** | dev.cannabrands.app | staging.cannabrands.app | cannabrands.app |
| **App Replicas** | 1 | 2 | 3 |
| **DB Replicas** | 1 | 1 | 2 (HA) |
| **DB Storage** | 10Gi | 50Gi | 100Gi (SSD) |
| **Docker Image** | `:dev` | `:staging` | `:latest` |
| **Debug Mode** | Enabled | Disabled | Disabled |
| **Log Level** | debug | info | warning |
| Resource | Development | Production |
|----------|-------------|------------|
| **Namespace** | `cannabrands-dev` | `cannabrands-prod` |
| **Domain** | dev.cannabrands.app | cannabrands.app |
| **App Replicas** | 1 | 3 |
| **DB Storage** | 10Gi | 100Gi (SSD) |
| **Docker Image** | `dev-{SHA}` | `prod-{SHA}` |
| **Debug Mode** | Enabled | Disabled |
| **Log Level** | debug | warning |
| **Seeders** | Run on deploy | Never |
---
@@ -255,49 +260,11 @@ Visit: https://dev.cannabrands.app
---
## Deploying to Staging
### 1. Deploy Staging Environment
```bash
kubectl apply -k k8s/overlays/staging
```
### 2. Verify Deployment
```bash
kubectl get all -n staging
kubectl get pods -n staging
```
### 3. Restore Sanitized Production Data
Follow the database strategy guide in `docs/DATABASE_STRATEGY.md`:
```bash
# Run sanitization script (see docs/DATABASE_STRATEGY.md)
./scripts/sanitize-production-for-staging.sh
```
### 4. Run Migrations
```bash
kubectl exec -it deployment/app -n staging -- php artisan migrate --force
```
### 5. Access Staging Site
Point DNS for `staging.cannabrands.app` to cluster ingress IP.
Visit: https://staging.cannabrands.app
---
## Deploying to Production
### 1. Pre-Deployment Checklist
- [ ] Staging is stable and tested
- [ ] Changes tested on dev.cannabrands.app
- [ ] All tests passing in CI
- [ ] Production secrets created and verified
- [ ] Database backup completed (if upgrading)

View File

@@ -8,9 +8,29 @@ spec:
spec:
initContainers:
- name: migrate
# Image tag updated by CI/CD pipeline via kubectl set image
# Format: prod-{SHA} (e.g., prod-abc1234)
image: code.cannabrands.app/cannabrands/hub:latest
command: ["/bin/sh", "-c"]
args:
- |
echo "Publishing vendor assets..."
php artisan vendor:publish --tag=public --force
echo "Publishing Filament assets..."
php artisan filament:assets
echo "Optimizing Laravel..."
php artisan optimize
echo "Optimizing Filament..."
php artisan filament:optimize
echo "Setting up storage directories..."
mkdir -p /var/www/html/storage/framework/{sessions,views,cache}
chmod -R 775 /var/www/html/storage
echo "Running migrations..."
php artisan migrate --force
echo "Init container complete!"
containers:
- name: app
# Image tag updated by CI/CD pipeline via kubectl set image
image: code.cannabrands.app/cannabrands/hub:latest
resources:
requests:

View File

@@ -1,14 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: cannabrands-hub
spec:
replicas: 2 # 2 replicas for staging
template:
spec:
initContainers:
- name: migrate
image: code.cannabrands.app/cannabrands/hub:staging
containers:
- name: app
image: code.cannabrands.app/cannabrands/hub:staging

View File

@@ -1,20 +0,0 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: cannabrands-hub
spec:
tls:
- hosts:
- staging.cannabrands.app
secretName: staging-tls-secret
rules:
- host: staging.cannabrands.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: cannabrands-hub
port:
number: 80

View File

@@ -1,31 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cannabrands-staging
resources:
- ../../base
patches:
- path: deployment-patch.yaml
- path: ingress-patch.yaml
- path: postgres-patch.yaml
configMapGenerator:
- name: app-config
behavior: merge
literals:
- APP_ENV=staging
- APP_DEBUG=false
- APP_URL=https://staging.cannabrands.app
- ASSET_URL=https://staging.cannabrands.app
- DB_HOST=postgres.cannabrands-staging.svc.cluster.local
- DB_DATABASE=cannabrands_staging
- REDIS_HOST=redis.cannabrands-staging.svc.cluster.local
- LOG_LEVEL=info
- TELESCOPE_ENABLED=true
- name: postgres-config
behavior: merge
literals:
- database=cannabrands_staging

View File

@@ -1,13 +0,0 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi # Medium for staging