# 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 |