Add CI/CD pipeline with Gitea Actions and Portainer deployment
Some checks failed
Build and Push Docker Image / build (push) Has been cancelled

- Create Gitea Actions workflow for automated Docker builds on push to main
- Add docker-compose.portainer.yml for production Portainer deployment
- Create comprehensive DEPLOYMENT.md guide with step-by-step instructions
- Update CLAUDE.md with CI/CD pipeline documentation

Images are built and pushed to Gitea registry at:
code.puffinoffset.com/matt/puffin-app:latest
code.puffinoffset.com/matt/puffin-app:main-<sha>

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Matt 2025-10-29 13:31:04 +01:00
parent 01b232f909
commit 1c9c570ece
4 changed files with 370 additions and 4 deletions

View File

@ -0,0 +1,47 @@
name: Build and Push Docker Image
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: code.puffinoffset.com
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: code.puffinoffset.com/matt/puffin-app
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=code.puffinoffset.com/matt/puffin-app:buildcache
cache-to: type=registry,ref=code.puffinoffset.com/matt/puffin-app:buildcache,mode=max
- name: Image digest
run: echo "Image pushed with digest ${{ steps.build.outputs.digest }}"

View File

@ -92,11 +92,58 @@ VITE_FORMSPREE_OFFSET_ID= # Offset order form endpoint
### Testing Strategy ### Testing Strategy
- Unit tests using Vitest and React Testing Library - Unit tests using Vitest and React Testing Library
- Test files in `__tests__` directories - Test configuration in `vitest.config.ts` with jsdom environment
- Run with `npm test` - Setup file at `src/test/setup.ts`
- Test files use pattern `*.test.ts` or `*.test.tsx`
- Run all tests: `npm test`
- Tests run with global test APIs enabled
### Build & Deployment ### Build & Deployment
- Production builds output to `dist/` - Production builds output to `dist/` directory
- Vite build configuration includes:
- Source maps enabled for debugging
- Code splitting with vendor chunk (React/React-DOM)
- Lucide-react excluded from optimizeDeps for compatibility
- Docker deployment uses Nginx to serve static files on port 3800 - Docker deployment uses Nginx to serve static files on port 3800
- Host Nginx reverse proxy configuration available in `nginx-host.conf` - Host Nginx reverse proxy configuration available in `nginx-host.conf`
- PWA manifest and service worker for mobile app installation - PWA manifest (`public/manifest.json`) and service worker (`public/sw.js`) for mobile app installation
### CI/CD Pipeline
**Automated Builds**:
- Gitea Actions workflow at `.gitea/workflows/build-deploy.yml`
- Triggers on push to `main` branch
- Builds multi-stage Docker image (Node build → Nginx serve)
- Pushes to Gitea container registry at `code.puffinoffset.com/matt/puffin-app`
**Image Tagging Strategy**:
- `latest`: Always points to most recent main branch build
- `main-<commit-sha>`: Specific commit version for rollbacks
**Container Registry**:
- Gitea's built-in Docker registry
- Authentication via Gitea credentials
- Registry URL: `code.puffinoffset.com`
**Deployment**:
- Manual deployment via Portainer
- Use `docker-compose.portainer.yml` for production stack configuration
- Environment variables mounted via `.env` file volume
- Detailed deployment instructions in `DEPLOYMENT.md`
**Workflow Optimization**:
- Docker buildx for multi-platform support
- Layer caching to speed up builds
- Separate build cache stored in registry
### Key Implementation Details
**Routing without React Router**: App uses state-based routing via `currentPage` state and `window.history.pushState()`. The `/mobile-app` route is detected by checking `window.location.pathname` and renders only `MobileCalculator` without the standard layout.
**Sample Vessel Data**: A hardcoded `sampleVessel` object is used as default vessel data in `App.tsx` (lines 17-24) since AIS client currently returns mock data.
**Analytics Integration**: `utils/analytics.ts` tracks page views via Google Analytics. Called in `App.tsx` useEffect when route changes.
**Currency Support**: `utils/currencies.ts` provides multi-currency conversion. Calculator components support USD, EUR, GBP selection.
**Error Handling**: `ErrorBoundary.tsx` component wraps the app to catch React rendering errors gracefully.

243
DEPLOYMENT.md Normal file
View File

@ -0,0 +1,243 @@
# Puffin Offset - Deployment Guide
This guide covers deploying the Puffin Offset application using Gitea Actions for automated builds and Portainer for container orchestration.
## Architecture Overview
```
Push to main → Gitea Actions → Build Docker Image → Push to Registry → Manual Deploy via Portainer
```
## CI/CD Pipeline
### Automated Build Process
When code is pushed to the `main` branch:
1. **Gitea Actions triggers** automatically
2. **Docker image is built** using the multi-stage Dockerfile
3. **Image is pushed** to Gitea's container registry with two tags:
- `latest` - Always points to the most recent build
- `main-<commit-sha>` - Specific commit for rollback capability
### Registry Location
Images are stored at:
```
code.puffinoffset.com/matt/puffin-app:latest
code.puffinoffset.com/matt/puffin-app:main-<sha>
```
## Portainer Deployment
### Prerequisites
1. **Portainer installed and accessible**
2. **Gitea registry credentials configured in Portainer**
3. **Environment variables prepared** (see below)
### Step 1: Configure Gitea Registry in Portainer
1. Navigate to **Portainer → Registries**
2. Click **"Add registry"**
3. Configure:
- **Name**: `Gitea - code.puffinoffset.com`
- **Registry URL**: `code.puffinoffset.com`
- **Authentication**: Enabled
- **Username**: Your Gitea username (e.g., `matt`)
- **Password**: Gitea access token or password
4. Click **"Add registry"**
### Step 2: Prepare Environment Variables
Create a `.env` file on your server with the required variables:
```bash
# Example: /path/to/puffin-app/.env
VITE_WREN_API_TOKEN=your-wren-api-token
VITE_FORMSPREE_CONTACT_ID=your-formspree-contact-id
VITE_FORMSPREE_OFFSET_ID=your-formspree-offset-id
```
**Note**: The env.sh script in the Docker image will inject these at runtime.
### Step 3: Create Portainer Stack
#### Option A: Using Portainer UI
1. Navigate to **Portainer → Stacks**
2. Click **"Add stack"**
3. Configure:
- **Name**: `puffin-app`
- **Build method**: "Web editor"
4. Paste the contents of `docker-compose.portainer.yml`
5. In **Environment variables** section, add:
- Name: `ENV_FILE_PATH`
- Value: `/path/to/your/.env`
6. Click **"Deploy the stack"**
#### Option B: Using Git Repository
1. Navigate to **Portainer → Stacks**
2. Click **"Add stack"**
3. Configure:
- **Name**: `puffin-app`
- **Build method**: "Repository"
- **Repository URL**: `https://code.puffinoffset.com/matt/puffin-app.git`
- **Repository reference**: `refs/heads/main`
- **Compose path**: `docker-compose.portainer.yml`
- **Authentication**: Configure with your Gitea credentials
4. Click **"Deploy the stack"**
### Step 4: Verify Deployment
1. Check that the container is running:
- Navigate to **Portainer → Containers**
- Look for `puffin-app-web-1` or similar
- Status should be "running"
2. Test the application:
- Access: `http://your-server:3800`
- Or via your Nginx reverse proxy configuration
3. Check logs if needed:
- Click on the container
- Select **"Logs"** tab
## Updating to New Versions
### Method 1: Using Portainer UI (Recommended)
1. Navigate to **Portainer → Stacks**
2. Click on **puffin-app** stack
3. Click **"Update the stack"**
4. Enable **"Pull latest image version"**
5. Enable **"Re-pull image and redeploy"**
6. Click **"Update"**
Portainer will:
- Pull the latest image from Gitea registry
- Recreate the container with the new image
- Maintain your environment variables and configuration
### Method 2: Pull Specific Version
To rollback or deploy a specific commit:
1. Edit the stack in Portainer
2. Change the image tag from `latest` to `main-<commit-sha>`
```yaml
image: code.puffinoffset.com/matt/puffin-app:main-abc1234
```
3. Update the stack
## Monitoring Build Status
### Check Gitea Actions
1. Go to your Gitea repository: `https://code.puffinoffset.com/matt/puffin-app`
2. Navigate to **"Actions"** tab
3. View recent workflow runs
4. Click on a run to see detailed logs
### Verify Image in Registry
1. In Gitea, navigate to **"Packages"** or **"Registry"**
2. Look for `puffin-app` images
3. Verify tags: `latest` and `main-<sha>` tags should be present
## Troubleshooting
### Build Failed in Gitea Actions
**Check the workflow logs:**
1. Go to Gitea → puffin-app → Actions
2. Click on the failed run
3. Review error messages
**Common issues:**
- Registry authentication failed: Check secrets.GITHUB_TOKEN is available
- Build errors: Review Dockerfile and build logs
- Network issues: Check runner connectivity
### Image Pull Failed in Portainer
**Error:** `unauthorized: authentication required`
**Solution:**
1. Verify registry is configured in Portainer (Step 1)
2. Check credentials are correct
3. Ensure registry URL is `code.puffinoffset.com` (no `https://`)
### Container Won't Start
**Check environment variables:**
1. Verify `.env` file exists on the host
2. Ensure volume mount path is correct in docker-compose.portainer.yml
3. Check container logs for missing env var errors
**Check port conflicts:**
1. Ensure port 3800 is not already in use
2. Run `netstat -tuln | grep 3800` on the host
### Application Not Accessible
**Verify container is running:**
```bash
docker ps | grep puffin-app
```
**Check nginx reverse proxy:**
- Review host Nginx configuration (`nginx-host.conf`)
- Ensure proxy_pass points to correct container port
- Test direct access: `http://server-ip:3800`
**Check firewall:**
```bash
# Example for ufw
sudo ufw status
sudo ufw allow 3800/tcp
```
## Environment Variables Reference
| Variable | Description | Required |
|----------|-------------|----------|
| `VITE_WREN_API_TOKEN` | Wren Climate API authentication token | Yes |
| `VITE_FORMSPREE_CONTACT_ID` | Formspree contact form endpoint ID | Yes |
| `VITE_FORMSPREE_OFFSET_ID` | Formspree offset order form endpoint ID | Yes |
| `NODE_ENV` | Node environment (set to `production`) | Auto-set |
## Rollback Procedure
To rollback to a previous version:
1. Find the commit SHA of the working version
2. In Portainer, edit the stack
3. Change image tag to `main-<previous-sha>`
4. Update the stack
Example:
```yaml
# Before (current broken version)
image: code.puffinoffset.com/matt/puffin-app:latest
# After (rollback to specific commit)
image: code.puffinoffset.com/matt/puffin-app:main-ab0dbbd
```
## Security Best Practices
1. **Never commit `.env` file to git** (already in .gitignore)
2. **Use Gitea access tokens** instead of passwords for registry auth
3. **Restrict registry access** to necessary users
4. **Review Gitea Actions logs** for sensitive data exposure
5. **Keep base images updated** (node:20-alpine, nginx:alpine)
## Next Steps
- Set up monitoring/alerting for failed builds
- Configure automatic backups of `.env` file
- Implement health checks in docker-compose
- Set up SSL certificates via Let's Encrypt
- Configure log aggregation for production debugging

View File

@ -0,0 +1,29 @@
version: '3.8'
services:
web:
image: code.puffinoffset.com/matt/puffin-app:latest
ports:
- "3800:3800"
environment:
- NODE_ENV=production
restart: unless-stopped
volumes:
# Mount .env file for runtime environment variable injection
- ./.env:/usr/share/nginx/html/.env:ro
# Optional: override nginx config if needed
# - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
# Production deployment notes:
# 1. Ensure .env file exists on the host with required variables:
# - VITE_WREN_API_TOKEN
# - VITE_FORMSPREE_CONTACT_ID
# - VITE_FORMSPREE_OFFSET_ID
#
# 2. Configure Gitea registry authentication in Portainer before deploying
#
# 3. To update to new image:
# - Navigate to stack in Portainer
# - Click "Update the stack"
# - Enable "Pull latest image version"
# - Click "Update"