This commit is contained in:
Daveanand Mannie
2026-02-21 21:40:20 -05:00
commit e12d0038ff
4 changed files with 304 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
app-local.ini
docker-compose.yml

165
README.md Normal file
View File

@@ -0,0 +1,165 @@
# Gitea on Dokploy
Self-hosted Gitea deployment running on Dokploy with a local Postgres database.
- **Domain**: `gitea.routinedevelopment.ca`
- **SSH**: Port 2222 (built-in SSH server, HTTP git disabled)
- **Database**: Local Postgres 16 (container `gitea-db`)
- **Volumes**: `gitea-data` (repos, LFS, avatars, SSH keys), `db-data` (Postgres)
## Setup (Fresh Install)
1. Install Dokploy on a clean Ubuntu server:
```bash
curl -sSL https://dokploy.com/install.sh | sh
```
2. Create a new Project in Dokploy called "Gitea".
3. Create a Docker Compose service inside the project. Paste the contents of `docker-compose.yml`.
4. Add the environment variable in Dokploy's UI:
```
POSTGRES_PASSWORD=your_strong_password
```
5. Deploy. Let it complete the first boot.
6. Point your DNS A record for `gitea.routinedevelopment.ca` to the server IP.
7. In Dokploy's UI, go to the Gitea compose service → **Domains** tab. Add `gitea.routinedevelopment.ca` with container port `3000`, HTTPS enabled, and Let's Encrypt certificate. The docs say Traefik labels alone should work, but in practice you may need to also add the domain via the UI.
8. Redeploy so Traefik picks up the domain and issues the Let's Encrypt certificate.
### Domain Troubleshooting
If the domain doesn't work after deploying:
- **DNS must resolve first**: Let's Encrypt needs to reach the server. Make sure your DNS/reserved IP points to the server *before* adding the domain in Dokploy. If added too early, recreate the domain or restart Traefik.
- **`dokploy-network` is required**: Traefik runs on `dokploy-network`. If your compose service isn't on that network, Traefik can't reach it even if the labels are correct. The compose file in this repo already handles this.
- **Reserved IP mismatch**: If using a DigitalOcean reserved IP, Dokploy may complain that the domain resolves to the reserved IP instead of the droplet's direct IP. This is fine — traffic still reaches the server. Deploy anyway.
- **Redeploy after any domain change**: Unlike Applications, Docker Compose services require a redeploy for domain changes to take effect.
## Restore from Backup
If restoring onto a fresh Dokploy instance after the initial deploy:
1. SCP the backup files to the server:
```bash
scp gitea_db_backup.dump user@server:~/
scp gitea_files_backup.tar.gz user@server:~/
scp app.ini user@server:~/ # the REAL one with secrets, not the template
```
2. SSH into the server and run the restore script:
```bash
chmod +x restore.sh
./restore.sh
```
3. Verify at `https://gitea.routinedevelopment.ca` — all repos, users, and orgs should be present.
## Backup
Run from the Dokploy server:
```bash
chmod +x backup.sh
./backup.sh
```
Backups are saved to `~/gitea-backups/` with timestamps. Each backup creates three files:
- `gitea_db_YYYYMMDD_HHMMSS.dump` — Postgres database dump
- `gitea_files_YYYYMMDD_HHMMSS.tar.gz` — Repositories, LFS, avatars, attachments, SSH keys
- `app_YYYYMMDD_HHMMSS.ini` — Current app.ini config (contains secrets)
You can specify a custom backup directory:
```bash
./backup.sh /path/to/backups
```
**Recommended**: Push backups offsite (S3, DigitalOcean Spaces, etc.). Don't rely solely on the same disk.
## Automated Backups (Optional)
Add a cron job on the server to run backups daily:
```bash
crontab -e
```
```
0 3 * * * /path/to/backup.sh /home/user/gitea-backups >> /var/log/gitea-backup.log 2>&1
```
## Secrets
The `app.ini` in this repo has `<CHANGE_ME>` placeholders. The real values you need to set:
| Key | Location | Description |
|-----|----------|-------------|
| `LFS_JWT_SECRET` | `[server]` | JWT secret for LFS |
| `INTERNAL_TOKEN` | `[security]` | Internal API token |
| `JWT_SECRET` | `[oauth2]` | OAuth2 JWT secret |
| `PASSWD` | `[database]` | Must match `POSTGRES_PASSWORD` env var |
Keep your real `app.ini` as `app.ini.local` (gitignored) or in a password manager.
## SSH Remotes
Since HTTP git is disabled, all clones use SSH on port 2222:
```bash
git clone ssh://git@gitea.routinedevelopment.ca:2222/org/repo.git
```
Note: The container user is `git`, not `gitea`. If migrating from baremetal, update your remotes:
```bash
git remote set-url origin ssh://git@gitea.routinedevelopment.ca:2222/YourOrg/your-repo.git
```
## GitHub Mirror
To push-mirror a repo to GitHub:
1. Ensure the container has an SSH key:
```bash
docker exec gitea ls /data/git/.ssh/id_ed25519.pub
```
If not, generate one:
```bash
docker exec gitea ssh-keygen -t ed25519 -f /data/git/.ssh/id_ed25519 -N ""
docker exec gitea chown git:git /data/git/.ssh/id_ed25519
```
2. Add the public key as a deploy key (with write access) on your GitHub repo.
3. Set up a post-receive hook or cron to push.
The SSH key lives in the `gitea-data` volume and persists across redeploys.
## Volume Info
| Volume | Contents | Path in container |
|--------|----------|-------------------|
| `gitea-data` | Repos, LFS, avatars, config, SSH keys | `/data` |
| `db-data` | Postgres data | `/var/lib/postgresql/data` |
Volumes persist across redeploys. They are only destroyed by `docker compose down -v` or `docker volume rm`. **Never run `down -v` in production.**
Inspect volumes:
```bash
docker volume ls | grep gitea
docker volume inspect <volume_name>
```

View File

@@ -0,0 +1,44 @@
services:
gitea:
image: gitea/gitea:latest
environment:
- USER_UID=1000
- USER_GID=1000
volumes:
- gitea-data:/data
ports:
- "2222:2222"
networks:
- dokploy-network
- internal
labels:
- "traefik.enable=true"
- "traefik.docker.network=dokploy-network"
- "traefik.http.routers.gitea.rule=Host(`gitea.routinedevelopment.ca`)"
- "traefik.http.routers.gitea.entrypoints=websecure"
- "traefik.http.routers.gitea.tls.certresolver=letsencrypt"
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
depends_on:
- db
restart: unless-stopped
db:
image: postgres:16
environment:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=gitea
volumes:
- db-data:/var/lib/postgresql/data
networks:
- internal
restart: unless-stopped
networks:
dokploy-network:
external: true
internal:
volumes:
gitea-data:
db-data:

92
restore.sh Normal file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# Gitea Restore Script for Dokploy
# =============================================================================
#
# Prerequisites:
# - Dokploy is installed and running
# - The Gitea compose stack is deployed (first boot completed)
# - You have gitea_db_backup.dump and gitea_files_backup.tar.gz in ~/
# - Your app.ini (with real secrets) is in ~/
#
# Usage:
# chmod +x restore.sh
# ./restore.sh
#
# =============================================================================
# --- Configuration ---
# Update these if your container names differ
GITEA_CONTAINER="gitea"
DB_CONTAINER="gitea-db"
DB_USER="gitea"
DB_NAME="gitea"
BACKUP_DIR="$HOME"
DB_DUMP="$BACKUP_DIR/gitea_db_backup.dump"
FILES_BACKUP="$BACKUP_DIR/gitea_files_backup.tar.gz"
APP_INI="$BACKUP_DIR/app.ini"
# --- Preflight checks ---
echo "=== Preflight checks ==="
for file in "$DB_DUMP" "$FILES_BACKUP" "$APP_INI"; do
if [[ ! -f "$file" ]]; then
echo "ERROR: $file not found"
exit 1
fi
done
if ! docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
echo "ERROR: Container $DB_CONTAINER is not running"
exit 1
fi
if ! docker ps --format '{{.Names}}' | grep -q "^${GITEA_CONTAINER}$"; then
echo "ERROR: Container $GITEA_CONTAINER is not running"
exit 1
fi
echo "All checks passed."
# --- Restore database ---
echo ""
echo "=== Restoring database ==="
docker cp "$DB_DUMP" "$DB_CONTAINER":/tmp/gitea_db_backup.dump
docker exec -it "$DB_CONTAINER" pg_restore \
-U "$DB_USER" -d "$DB_NAME" \
--no-owner --no-acl \
/tmp/gitea_db_backup.dump
echo "Database restored."
# --- Restore files ---
echo ""
echo "=== Restoring files ==="
docker cp "$FILES_BACKUP" "$GITEA_CONTAINER":/tmp/gitea_files_backup.tar.gz
docker exec -it "$GITEA_CONTAINER" bash -c '
cd /tmp
tar xzf gitea_files_backup.tar.gz --strip-components=3
cp -r repositories /data/git/
[ -d lfs ] && cp -r lfs /data/git/
[ -d avatars ] && cp -r avatars /data/gitea/
[ -d attachments ] && cp -r attachments /data/gitea/
chown -R git:git /data/git /data/gitea
rm -rf /tmp/repositories /tmp/lfs /tmp/avatars /tmp/attachments /tmp/gitea_files_backup.tar.gz
'
echo "Files restored."
# --- Inject app.ini ---
echo ""
echo "=== Injecting app.ini ==="
docker cp "$APP_INI" "$GITEA_CONTAINER":/data/gitea/conf/app.ini
echo "Config injected."
# --- Restart ---
echo ""
echo "=== Restarting Gitea ==="
docker restart "$GITEA_CONTAINER"
echo "Done! Gitea should be available shortly."
echo ""
echo "Verify with: curl -s http://\$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $GITEA_CONTAINER):3000 | head -5"