8.5 KiB
Deployment Guide
Woodpecker builds the site, pushes the image to Gitea’s container registry, then triggers a Portainer stack redeploy via webhook. The stack on your Linode VPS pulls the new image and recreates the container.
Portainer stack options
You can run the stack in Portainer in two ways; both use the pre-built image (Option A).
| Option | Description | When to use |
|---|---|---|
| Git repository | Portainer pulls the stack definition from this repo (docker-compose.yml). Compose path: docker-compose.yml. On webhook: Portainer pulls latest compose + image and redeploys. |
Stack definition (ports, env) lives in git; one repo for app + stack. |
| Web editor | You paste the compose YAML in Portainer. No compose file in the repo. On webhook: Portainer pulls the image and redeploys. | You prefer to manage stack only in Portainer and keep the repo app-only. |
This repo includes docker-compose.yml for the Repository option. The compose file only references the image (git.mifi.dev/mifi-ventures/landing:latest); it does not build. In Portainer, enable Re-pull image so each webhook pulls the new :latest and recreates the stack.
Prerequisites
- Gitea with Woodpecker CI and container registry enabled
- Linode VPS with Docker; Portainer installed and managing that environment
- Portainer stack created (Repository pointing at this repo, or Web editor with equivalent compose)
Step-by-Step Setup
1. Prepare VPS and Portainer
- Install Docker on the Linode VPS and run Portainer (e.g. as a container or on the host).
- In Portainer, add your Gitea registry: Settings → Registries → Add registry. Use the same URL and credentials you use for
REGISTRY_*in Woodpecker so the host can pullgit.mifi.dev/mifi-ventures/landing:latest.
2. Create the Portainer stack
If using Git repository (recommended):
- Stacks → Add stack → name (e.g.
landing). - Choose Git repository.
- Repository URL:
https://git.mifi.dev/mifi-ventures/landing.git(or your clone URL). Add credentials if the repo is private. - Repository reference:
main. - Compose path:
docker-compose.yml. - Enable GitOps updates → Webhook. Copy the webhook URL for the Woodpecker secret
portainer_webhook_url. - Enable Re-pull image so each webhook pulls the new image.
- Optionally set stack env vars:
LANDING_PORT=8080(or leave default in compose). - Deploy the stack.
If using Web editor:
- Stacks → Add stack → name (e.g.
landing). - Choose Web editor and paste the same structure as
docker-compose.yml(service withimage: git.mifi.dev/mifi-ventures/landing:latest,pull_policy: always,ports,restart). - Enable the stack webhook, copy the URL for
portainer_webhook_url. - Deploy the stack.
3. Gitea container registry
Use Gitea’s built-in container registry. Image path:
- Registry URL:
git.mifi.dev - Image (REGISTRY_REPO):
git.mifi.dev/mifi-ventures/landing - Create a Gitea token or use your password with package:write (or equivalent) for the
mifi-ventures/landingpackage. Use that asregistry_passwordin Woodpecker.
4. Configure Woodpecker Secrets
In Gitea → Repository → Settings → Secrets (Woodpecker):
| Name | Value |
|---|---|
registry_password |
Gitea token or password (package write to the repo’s registry) |
portainer_webhook_url |
Portainer stack webhook URL (from step 2) |
5. Configure Woodpecker Variables
In Gitea → Repository → Settings → Variables (Woodpecker):
| Name | Value |
|---|---|
REGISTRY_URL |
git.mifi.dev |
REGISTRY_REPO |
git.mifi.dev/mifi-ventures/landing |
REGISTRY_USERNAME |
Your Gitea username |
6. Test Deployment
Local Test Build
# Clone repo
git clone https://git.mifi.dev/mifi-ventures/landing.git
cd landing
# Build locally
docker build -t test .
# Run locally
docker run -d -p 8080:80 --name test test
# Test
curl http://localhost:8080
# Cleanup
docker stop test && docker rm test
Trigger CI/CD
# Make a small change
echo "# Test deployment" >> README.md
# Commit and push to main
git add README.md
git commit -m "test: trigger deployment"
git push origin main
# Watch pipeline in Woodpecker UI (Gitea → Repository → Pipelines).
# After build + push, deploy step calls Portainer webhook; Portainer redeploys the stack.
7. Verify Deployment
After the pipeline completes:
- In Portainer, open the stack and confirm the service is running and the image was pulled.
- From your machine:
curl http://your-vps-ip:8080(or the port you set in the stack). - In a browser: visit
http://your-vps-ip:8080(or your domain if you use a reverse proxy).
Port Configuration
The stack compose uses LANDING_PORT (default 8080). Set it in Portainer stack env vars or in docker-compose.yml:
- Direct port 80: set
LANDING_PORT=80in the stack. - Custom port (e.g. behind Traefik): set
LANDING_PORT=8080and proxy 80/443 → 8080.
Note: With Traefik, security headers are added at the proxy level.
Traefik Integration Example
If using Traefik as reverse proxy, add labels to the service in docker-compose.yml:
services:
landing:
image: ${LANDING_IMAGE:-git.mifi.dev/mifi-ventures/landing:latest}
pull_policy: always
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.mifi.rule=Host(`mifi.ventures`)"
- "traefik.http.routers.mifi.entrypoints=websecure"
- "traefik.http.routers.mifi.tls.certresolver=letsencrypt"
- "traefik.http.services.mifi.loadbalancer.server.port=80"
networks:
- traefik-public
networks:
traefik-public:
external: true
(Remove or omit ports if Traefik is the only entry point.)
Common Issues
Registry Login Failed (Woodpecker push)
- Ensure
REGISTRY_URLisgit.mifi.devandREGISTRY_REPOisgit.mifi.dev/mifi-ventures/landing. - Use a Gitea token with package write permission (or your password if allowed).
- Test locally:
echo "TOKEN" | docker login git.mifi.dev -u USERNAME --password-stdin.
Portainer Cannot Pull Image
- In Portainer, add the Gitea registry under Settings → Registries with the same URL and credentials.
- Ensure the stack’s Re-pull image (or equivalent) is enabled so redeploys pull the new image.
Webhook Returns 4xx/5xx
- Confirm the webhook URL is correct and the stack exists.
- In Portainer, open the stack → Webhook and verify the URL matches the secret
portainer_webhook_url.
Container Won’t Start (Portainer)
- In Portainer, open the stack → service → Logs.
- Check for port conflicts on the host (e.g. another service using the same port).
- Ensure the Gitea registry is added in Portainer so the image can be pulled.
Rollback Procedure
If a bad image was deployed:
- Portainer: Open the stack → Redeploy with “Re-pull image” off, or edit the stack to use a specific image tag (e.g.
git.mifi.dev/mifi-ventures/landing:<commit-sha>) if you tag by SHA in Woodpecker. - Or in Gitea, revert the commit and push to
main; the pipeline will build and push a new image, then the webhook will redeploy. Ensure the reverted commit builds successfully.
Monitoring
- Portainer: Stack → service → Logs, Inspect, Stats.
- On the host (if you have SSH):
docker ps,docker logs <container>,docker stats <container>. - Nginx logs (from host):
docker exec <container> tail -f /var/log/nginx/access.log(anderror.log). - Disk: Portainer → Host → disk usage; or on host:
docker system df,docker image prune -af --filter "until=72h".
Security Checklist
- Registry (Gitea) uses authentication; token stored only in Woodpecker secrets
- Portainer webhook URL stored only in Woodpecker secrets (not in repo)
- VPS firewall configured (ufw or iptables)
- Traefik (or reverse proxy) handles TLS termination if used
- Container runs as non-root user (nginx user in the image)
- Regular security updates on the host (
apt update && apt upgrade)
Next Steps
- Set up monitoring (Prometheus + Grafana)
- Configure automated backups
- Add Slack/Discord notifications to the Woodpecker pipeline
- Set up a staging stack in Portainer (e.g. different port or branch)
- Optionally tag images by commit SHA in Woodpecker for easier rollback in Portainer
Last Updated: 2026-01-30
Maintainer: Mike Fitzpatrick