From e12d0038ffbe0063827a663bd9e64eac7665288a Mon Sep 17 00:00:00 2001 From: Daveanand Mannie Date: Sat, 21 Feb 2026 21:40:20 -0500 Subject: [PATCH] [init] --- .gitignore | 3 + README.md | 165 +++++++++++++++++++++++++++++++++++++ docker-compose-example.yml | 44 ++++++++++ restore.sh | 92 +++++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docker-compose-example.yml create mode 100644 restore.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f380f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +app-local.ini +docker-compose.yml + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c3a0f5 --- /dev/null +++ b/README.md @@ -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 `` 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 +``` diff --git a/docker-compose-example.yml b/docker-compose-example.yml new file mode 100644 index 0000000..999472b --- /dev/null +++ b/docker-compose-example.yml @@ -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: diff --git a/restore.sh b/restore.sh new file mode 100644 index 0000000..ecada36 --- /dev/null +++ b/restore.sh @@ -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"