- Adjusted .prettierrc to ensure consistent formatting with a newline at the end of the file. - Reformatted eslint.config.mjs for improved readability by aligning array elements. - Updated pnpm-lock.yaml to use single quotes for consistency across dependencies. - Simplified worktree setup in .cursor/worktrees.json for cleaner configuration. - Enhanced documentation in .cursor/plans to clarify architecture refactoring. - Refactored various service files for improved readability and maintainability, including rate-limiting and auth services. - Updated imports and exports across multiple files for consistency and clarity. - Improved error handling and logging in service methods to enhance debugging capabilities. - Streamlined utility functions for better performance and maintainability across the domain packages.
487 lines
13 KiB
Markdown
487 lines
13 KiB
Markdown
# Complete Portainer Guide for Customer Portal
|
|
|
|
## Table of Contents
|
|
|
|
1. [Creating a Stack in Portainer](#creating-a-stack-in-portainer)
|
|
2. [Repository vs Upload vs Web Editor](#stack-creation-methods)
|
|
3. [Security Concerns & Best Practices](#security-concerns)
|
|
4. [Auto-Updating Images](#auto-updating-images)
|
|
5. [Recommended Setup for Production](#recommended-production-setup)
|
|
|
|
---
|
|
|
|
## Creating a Stack in Portainer
|
|
|
|
### Step 1: Access Portainer
|
|
|
|
1. Open Portainer UI (typically at `https://your-server:9443` or via Plesk)
|
|
2. Select your environment (usually "local" for Plesk)
|
|
3. Go to **Stacks** in the left sidebar
|
|
|
|
### Step 2: Create New Stack
|
|
|
|
Click **"+ Add stack"** button
|
|
|
|
You'll see three creation methods:
|
|
|
|
- **Web editor** - Paste compose file directly
|
|
- **Upload** - Upload a compose file
|
|
- **Repository** - Pull from Git repository
|
|
|
|
### Step 3: Configure the Stack
|
|
|
|
**Name:** `customer-portal` (lowercase, no spaces)
|
|
|
|
**Compose content:** Use one of the methods below
|
|
|
|
**Environment variables:** Add your configuration
|
|
|
|
### Step 4: Deploy
|
|
|
|
Click **"Deploy the stack"**
|
|
|
|
---
|
|
|
|
## Stack Creation Methods
|
|
|
|
### Method 1: Web Editor (Simplest)
|
|
|
|
**How:**
|
|
|
|
1. Select "Web editor"
|
|
2. Paste your `docker-compose.yml` content
|
|
3. Add environment variables manually or load from file
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Quick and simple
|
|
- ✅ No external dependencies
|
|
- ✅ Full control over content
|
|
|
|
**Cons:**
|
|
|
|
- ❌ Manual updates required
|
|
- ❌ No version control
|
|
- ❌ Easy to make mistakes when editing
|
|
|
|
**Best for:** Quick testing, simple deployments
|
|
|
|
---
|
|
|
|
### Method 2: Upload (Recommended for Your Case)
|
|
|
|
**How:**
|
|
|
|
1. Select "Upload"
|
|
2. Upload your `docker-compose.yml` file
|
|
3. Optionally upload a `.env` file for environment variables
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Version control on your local machine
|
|
- ✅ Can prepare and test locally
|
|
- ✅ No external network dependencies
|
|
- ✅ Works in air-gapped environments
|
|
|
|
**Cons:**
|
|
|
|
- ❌ Manual upload for each update
|
|
- ❌ Need to manage files locally
|
|
|
|
**Best for:** Production deployments with manual control
|
|
|
|
---
|
|
|
|
### Method 3: Repository (Git Integration)
|
|
|
|
**How:**
|
|
|
|
1. Select "Repository"
|
|
2. Enter repository URL (GitHub, GitLab, Bitbucket, etc.)
|
|
3. Specify branch and compose file path
|
|
4. Add authentication if private repo
|
|
|
|
**Example Configuration:**
|
|
|
|
```
|
|
Repository URL: https://github.com/your-org/customer-portal
|
|
Reference: main
|
|
Compose path: docker/portainer/docker-compose.yml
|
|
```
|
|
|
|
**For Private Repos:**
|
|
|
|
- Use a Personal Access Token (PAT) as password
|
|
- Or use deploy keys
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Version controlled
|
|
- ✅ Easy to update (just click "Pull and redeploy")
|
|
- ✅ Team can review changes via PR
|
|
- ✅ Audit trail of changes
|
|
|
|
**Cons:**
|
|
|
|
- ❌ Requires network access to repo
|
|
- ❌ Secrets in repo = security risk
|
|
- ❌ Need to manage repo access tokens
|
|
- ❌ Compose file changes require git push
|
|
|
|
**Best for:** Teams, CI/CD pipelines, frequent updates
|
|
|
|
---
|
|
|
|
### 📌 My Recommendation for Your Case
|
|
|
|
**Use: Upload + Environment Variables in Portainer UI**
|
|
|
|
Why:
|
|
|
|
1. Your compose file rarely changes (it's just orchestration)
|
|
2. Sensitive data stays in Portainer, not in Git
|
|
3. Image updates are done via environment variables
|
|
4. No external dependencies during deployment
|
|
|
|
---
|
|
|
|
## Security Concerns
|
|
|
|
### 🔴 Critical Security Issues
|
|
|
|
#### 1. Never Store Secrets in Git
|
|
|
|
```yaml
|
|
# ❌ BAD - Secrets in compose file
|
|
environment:
|
|
JWT_SECRET: "my-actual-secret-here"
|
|
DATABASE_URL: "postgresql://user:password@db/prod"
|
|
|
|
# ✅ GOOD - Use environment variables
|
|
environment:
|
|
JWT_SECRET: ${JWT_SECRET}
|
|
DATABASE_URL: ${DATABASE_URL}
|
|
```
|
|
|
|
#### 2. Never Store Secrets in Docker Images
|
|
|
|
```dockerfile
|
|
# ❌ BAD - Secrets baked into image
|
|
ENV JWT_SECRET="my-secret"
|
|
COPY secrets/ /app/secrets/
|
|
|
|
# ✅ GOOD - Mount at runtime
|
|
# (secrets passed via env vars or volume mounts)
|
|
```
|
|
|
|
#### 3. Portainer Access Control
|
|
|
|
```
|
|
⚠️ Portainer has full Docker access = root on the host
|
|
|
|
Best practices:
|
|
- Use strong passwords
|
|
- Enable 2FA if available
|
|
- Restrict network access to Portainer UI
|
|
- Use HTTPS only
|
|
- Create separate users with limited permissions
|
|
```
|
|
|
|
### 🟡 Medium Security Concerns
|
|
|
|
#### 4. Environment Variables in Portainer
|
|
|
|
```
|
|
Portainer stores env vars in its database.
|
|
This is generally safe, but consider:
|
|
|
|
- Portainer database is at /data/portainer.db
|
|
- Anyone with Portainer admin = sees all secrets
|
|
- Backup files may contain secrets
|
|
|
|
Mitigation:
|
|
- Limit Portainer admin access
|
|
- Use Docker secrets for highly sensitive data
|
|
- Encrypt backups
|
|
```
|
|
|
|
#### 5. Image Trust
|
|
|
|
```
|
|
⚠️ You're loading .tar files - verify their integrity
|
|
|
|
Best practice:
|
|
- Generate checksums when building
|
|
- Verify checksums before loading
|
|
- Use signed images if possible
|
|
```
|
|
|
|
Add to build script:
|
|
|
|
```bash
|
|
# Generate checksums
|
|
sha256sum portal-frontend.latest.tar > portal-frontend.latest.tar.sha256
|
|
sha256sum portal-backend.latest.tar > portal-backend.latest.tar.sha256
|
|
|
|
# Verify on server
|
|
sha256sum -c portal-frontend.latest.tar.sha256
|
|
sha256sum -c portal-backend.latest.tar.sha256
|
|
```
|
|
|
|
#### 6. Network Exposure
|
|
|
|
```yaml
|
|
# ❌ BAD - Database exposed to host
|
|
database:
|
|
ports:
|
|
- "5432:5432" # Accessible from outside!
|
|
|
|
# ✅ GOOD - Internal network only
|
|
database:
|
|
# No ports exposed - only accessible via portal-network
|
|
networks:
|
|
- portal-network
|
|
```
|
|
|
|
### 🟢 Good Security Practices (Already in Place)
|
|
|
|
Your current setup does these right:
|
|
|
|
- ✅ Non-root users in containers
|
|
- ✅ Health checks configured
|
|
- ✅ Database/Redis not exposed externally
|
|
- ✅ Secrets mounted as read-only volumes
|
|
- ✅ Production error messages hide sensitive info
|
|
|
|
---
|
|
|
|
## Auto-Updating Images
|
|
|
|
### Option 1: Watchtower (NOT Recommended for Production)
|
|
|
|
Watchtower automatically updates containers when new images are available.
|
|
|
|
```yaml
|
|
# Add to your stack (if using registry)
|
|
watchtower:
|
|
image: containrrr/watchtower
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
environment:
|
|
- WATCHTOWER_POLL_INTERVAL=300
|
|
- WATCHTOWER_CLEANUP=true
|
|
command: --include-stopped portal-frontend portal-backend
|
|
```
|
|
|
|
**Why NOT recommended:**
|
|
|
|
- ❌ No control over when updates happen
|
|
- ❌ No rollback mechanism
|
|
- ❌ Can break production unexpectedly
|
|
- ❌ Requires images in a registry (not .tar files)
|
|
|
|
We've disabled Watchtower in your compose:
|
|
|
|
```yaml
|
|
labels:
|
|
- "com.centurylinklabs.watchtower.enable=false"
|
|
```
|
|
|
|
---
|
|
|
|
### Option 2: Portainer Webhooks (Semi-Automatic)
|
|
|
|
Portainer can expose a webhook URL that triggers stack redeployment.
|
|
|
|
**Setup:**
|
|
|
|
1. Go to Stack → Settings
|
|
2. Enable "Webhook"
|
|
3. Copy the webhook URL
|
|
|
|
**Trigger from CI/CD:**
|
|
|
|
```bash
|
|
# In your GitHub Actions / GitLab CI
|
|
curl -X POST "https://your-portainer:9443/api/stacks/webhook/abc123"
|
|
```
|
|
|
|
**Workflow:**
|
|
|
|
```
|
|
Build Images → Push to Registry → Trigger Webhook → Portainer Redeploys
|
|
```
|
|
|
|
**Pros:**
|
|
|
|
- ✅ Controlled updates
|
|
- ✅ Integrated with CI/CD
|
|
- ✅ Can add approval gates
|
|
|
|
**Cons:**
|
|
|
|
- ❌ Requires images in a registry
|
|
- ❌ Webhook URL is a secret
|
|
- ❌ Limited rollback options
|
|
|
|
---
|
|
|
|
### Option 3: Manual Script (Recommended for Your Case) ✅
|
|
|
|
Since you're using `.tar` files (no registry), a manual update script is best:
|
|
|
|
```bash
|
|
# On your local machine after building:
|
|
./scripts/plesk/build-images.sh --tag v1.2.3
|
|
|
|
# Upload to server
|
|
scp portal-*.v1.2.3.tar user@server:/path/to/images/
|
|
|
|
# SSH and run update
|
|
ssh user@server "cd /path/to/portal && ./update-stack.sh v1.2.3"
|
|
```
|
|
|
|
**Make it a one-liner:**
|
|
|
|
```bash
|
|
# deploy.sh - Run locally
|
|
#!/bin/bash
|
|
TAG=$1
|
|
SERVER="user@your-server"
|
|
REMOTE_PATH="/var/www/vhosts/domain/portal"
|
|
|
|
# Build
|
|
./scripts/plesk/build-images.sh --tag "$TAG"
|
|
|
|
# Upload
|
|
scp portal-frontend.${TAG}.tar portal-backend.${TAG}.tar ${SERVER}:${REMOTE_PATH}/images/
|
|
|
|
# Deploy
|
|
ssh $SERVER "cd ${REMOTE_PATH} && ./update-stack.sh ${TAG}"
|
|
|
|
echo "✅ Deployed ${TAG}"
|
|
```
|
|
|
|
---
|
|
|
|
### Option 4: Use a Container Registry (Most Professional)
|
|
|
|
If you want auto-updates, use a registry:
|
|
|
|
**Free Options:**
|
|
|
|
- GitHub Container Registry (ghcr.io) - free for public repos
|
|
- GitLab Container Registry - free
|
|
- Docker Hub - 1 private repo free
|
|
|
|
**Setup:**
|
|
|
|
```bash
|
|
# Build and push
|
|
./scripts/plesk/build-images.sh --tag v1.2.3 --push ghcr.io/your-org
|
|
|
|
# Update compose to use registry
|
|
services:
|
|
frontend:
|
|
image: ghcr.io/your-org/portal-frontend:${TAG:-latest}
|
|
```
|
|
|
|
**Then use Watchtower or webhooks for auto-updates.**
|
|
|
|
---
|
|
|
|
## Recommended Production Setup
|
|
|
|
### For Your Current Situation (No Registry)
|
|
|
|
```
|
|
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ Local Dev │ │ Plesk Server │ │ Portainer │
|
|
│ │ │ │ │ │
|
|
│ 1. Build images │───▶│ 2. Load .tar │───▶│ 3. Update stack │
|
|
│ with tag │ │ files │ │ env vars │
|
|
│ │ │ │ │ │
|
|
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
▲ │
|
|
│ ▼
|
|
└──────────────────────────────────────────────┘
|
|
4. Verify & rollback if needed
|
|
```
|
|
|
|
**Steps:**
|
|
|
|
1. Build: `./scripts/plesk/build-images.sh --tag 20241201-abc`
|
|
2. Upload: `scp *.tar server:/path/images/`
|
|
3. Load: `docker load -i *.tar`
|
|
4. Update: Change `FRONTEND_IMAGE` and `BACKEND_IMAGE` in Portainer
|
|
5. Redeploy: Click "Update the stack" in Portainer
|
|
|
|
### For Future (With Registry)
|
|
|
|
```
|
|
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ GitHub │ │ GitHub │ │ Portainer │
|
|
│ (Code) │───▶│ Actions │───▶│ (Webhook) │
|
|
│ │ │ (Build & Push) │ │ │
|
|
└─────────────────┘ └────────┬─────────┘ └────────┬────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌────────────────┐ ┌────────────────┐
|
|
│ ghcr.io │ │ Plesk Server │
|
|
│ (Registry) │◀─────│ (Pull Image) │
|
|
└────────────────┘ └────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Reference: Portainer Stack Commands
|
|
|
|
### Via Portainer UI
|
|
|
|
| Action | Steps |
|
|
| ------------ | -------------------------------------------------- |
|
|
| Create stack | Stacks → Add stack → Configure → Deploy |
|
|
| Update stack | Stacks → Select → Editor → Update |
|
|
| Change image | Stacks → Select → Env vars → Change IMAGE → Update |
|
|
| View logs | Stacks → Select → Container → Logs |
|
|
| Restart | Stacks → Select → Container → Restart |
|
|
| Stop | Stacks → Select → Stop |
|
|
| Delete | Stacks → Select → Delete |
|
|
|
|
### Via CLI (on server)
|
|
|
|
```bash
|
|
# Navigate to stack directory
|
|
cd /path/to/portal
|
|
|
|
# View status
|
|
docker compose --env-file stack.env ps
|
|
|
|
# View logs
|
|
docker compose --env-file stack.env logs -f
|
|
|
|
# Restart
|
|
docker compose --env-file stack.env restart
|
|
|
|
# Update (after changing stack.env)
|
|
docker compose --env-file stack.env up -d
|
|
|
|
# Stop
|
|
docker compose --env-file stack.env down
|
|
|
|
# Stop and remove volumes (⚠️ DATA LOSS)
|
|
docker compose --env-file stack.env down -v
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Aspect | Recommendation |
|
|
| ------------------ | ------------------------------------------------------------------ |
|
|
| Stack creation | **Upload** method (version control locally, no secrets in git) |
|
|
| Secrets management | **Portainer env vars** or **mounted secrets volume** |
|
|
| Image updates | **Manual script** for now, migrate to **registry + webhook** later |
|
|
| Auto-updates | **Not recommended** for production; use controlled deployments |
|
|
| Rollback | Keep previous image tags, update env vars to rollback |
|