{"id":4915,"date":"2026-10-01T20:02:03","date_gmt":"2026-10-01T14:32:03","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/setting-up-a-self-hosted-gitea-server-with-docker\/"},"modified":"2026-10-01T20:02:03","modified_gmt":"2026-10-01T14:32:03","slug":"setting-up-a-self-hosted-gitea-server-with-docker","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/setting-up-a-self-hosted-gitea-server-with-docker\/","title":{"rendered":"Setting Up a Self-Hosted Gitea Server with Docker"},"content":{"rendered":"<p>Modern software delivery pipelines increasingly suffer from SaaS vendor lock-in, unpredictable pricing escalations, and latency bottlenecks during high-volume distributed code pushes. While bulky enterprise platforms such as GitLab often demand upwards of 4 GB to 8 GB of RAM merely to initialize basic daemons, developers seeking lean infrastructure on <a href=\"https:\/\/cpanelfree.com\">CpanelFree<\/a> can achieve full GitHub-grade autonomy on a fraction of the compute footprint. Setting up a high-performance self hosted gitea server inside Docker containers decouples data storage, isolates application runtimes, and delivers sub-millisecond Git transactions across enterprise environments.<\/p>\n<p><!-- more --><\/p>\n<h2>What is a Self-Hosted Gitea Server and Why Deploy with Docker?<\/h2>\n<div class=\"wp-block-group\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:20px 0;font-size:15px;line-height:1.6;color:#333\">\n<p><strong>Direct Answer:<\/strong> A self-hosted Gitea server is an ultra-lightweight, open-source Git forge built with Go that delivers comprehensive source control, code review, issue tracking, and CI\/CD pipelines at roughly 100 MB of baseline memory. Deploying Gitea via Docker guarantees containerized environment isolation, deterministic configuration management, seamless rolling version updates, and turnkey database segregation using PostgreSQL.<\/p>\n<\/div>\n<p>Traditional monolithic source code management platforms introduce complex host dependencies, runtime package conflicts, and fragile upgrade procedures. Gitea circumvents this complexity entirely by distributing a single, compiled Go binary capable of operating as an all-in-one web portal, Git transport listener, and package registry. When wrapped in Docker container primitives, sysadmins gain strict control over resource ceilings, network ingress, and persistent volume lifecycles without polluting the underlying host operating system.<\/p>\n<h2>Architectural Blueprint &amp; Topology Overview<\/h2>\n<p>In a resilient enterprise deployment, Gitea must never run as a solitary container relying on an embedded SQLite database. High-throughput code checkouts, parallel automated CI\/CD webhooks, and multi-developer team environments trigger concurrent file write locks that quickly degrade SQLite databases into catastrophic contention states.<\/p>\n<p>Our production-grade topology segregates concerns across three specialized container layers linked over an isolated internal bridge network:<\/p>\n<ul>\n<li><strong>Application Layer (Gitea Engine):<\/strong> Executes the rootless Gitea container, serving the responsive web UI, managing authentication, parsing webhooks, and orchestrating Git operations.<\/li>\n<li><strong>Relational Persistence Layer (PostgreSQL 16):<\/strong> Handles indexed queries, relational metadata, pull request tracking, and branch authorization states with optimized connection pooling.<\/li>\n<li><strong>Caching &amp; Worker Queue Layer (Redis 7):<\/strong> Absorbs short-lived session states, offloads avatar\/attachment memory caches, and queues background mail and webhook workers to maintain instant HTTP response times.<\/li>\n<li><strong>Ingress &amp; TLS Termination (Nginx Reverse Proxy):<\/strong> Terminates HTTPS certificates, applies rate limiting, accelerates HTTP\/2 multiplexing, and forwards raw Git SSH payloads seamlessly.<\/li>\n<\/ul>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note:<\/strong> Always enforce a dedicated non-root system user (e.g., UID 1000, GID 1000 named <code>git<\/code>) on the host machine matching the container user. This prevents permission collisions on bind-mounted directories and stops container-breakout vectors by ensuring the container runtime never writes host files with root privileges.<\/p>\n<\/blockquote>\n<h2>Host Preparation &amp; Linux Kernel Optimization<\/h2>\n<p>Before launching container runtimes, the underlying Linux kernel must be tuned to process high-concurrency Git operations. Heavy repository synchronizations and concurrent clone bursts can exhaust socket buffers and ephemeral port allocations if default Linux kernel limits are left unchanged.<\/p>\n<p>Create a dedicated sysctl configuration file at <code>\/etc\/sysctl.d\/99-gitea.conf<\/code> to apply optimized network buffers, enhanced backlog queues, and virtual memory parameters:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/sysctl.d\/99-gitea.conf\n# Production Linux Kernel Tuning for High-Concurrency Gitea Servers\n\n# Expand system-wide open file descriptor limits\nfs.file-max = 2097152\n\n# Enlarge socket listener queue for bursty Git clone and push operations\nnet.core.somaxconn = 65535\nnet.ipv4.tcp_max_syn_backlog = 8192\n\n# Expand local ephemeral port range for webhooks and database calls\nnet.ipv4.ip_local_port_range = 10240 65535\n\n# Allocate larger TCP socket memory buffers for multi-megabyte packfile streaming\nnet.core.rmem_max = 16777216\nnet.core.wmem_max = 16777216\nnet.ipv4.tcp_rmem = 4096 87380 16777216\nnet.ipv4.tcp_wmem = 4096 65536 16777216\n\n# Implement modern BBR congestion control and Fair Queuing\nnet.core.default_qdisc = fq\nnet.ipv4.tcp_congestion_control = bbr\n\n# Protect physical RAM by lowering swap aggressiveness\nvm.swappiness = 10\nvm.dirty_ratio = 15\nvm.dirty_background_ratio = 5<\/code><\/pre>\n<p>Apply these modifications immediately without rebooting by executing:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo sysctl --system<\/code><\/pre>\n<p>Next, configure security limits for your deployment user in <code>\/etc\/security\/limits.d\/99-gitea.conf<\/code> to prevent process exhaustion under load:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/security\/limits.d\/99-gitea.conf\ngit    soft    nofile    65535\ngit    hard    nofile    65535\ngit    soft    nproc     32768\ngit    hard    nproc     32768<\/code><\/pre>\n<h2>Directory Scaffolding and Permission Isolation<\/h2>\n<p>Create the persistent host directory tree under <code>\/srv\/gitea<\/code>. Isolate storage permissions strictly to the non-root <code>git<\/code> user (UID 1000, GID 1000):<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># Initialize system user and dedicated service directories\nsudo useradd -u 1000 -U -m -s \/bin\/bash git || true\nsudo mkdir -p \/srv\/gitea\/{data,config,postgres,redis}\nsudo chown -R 1000:1000 \/srv\/gitea\/data \/srv\/gitea\/config\nsudo chmod -R 750 \/srv\/gitea<\/code><\/pre>\n<h2>Production Docker Compose Specification<\/h2>\n<p>The following <code>docker-compose.yml<\/code> file orchestrates the Gitea container, PostgreSQL database engine, and Redis cache. It enforces strict container health checks, automated restart policies, static logging retention caps, and resource boundaries to prevent noisy-neighbor memory exhaustion.<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/srv\/gitea\/docker-compose.yml\nversion: '3.8'\n\nnetworks:\n  gitea_internal:\n    driver: bridge\n\nservices:\n  db:\n    image: postgres:16-alpine\n    container_name: gitea_db\n    restart: always\n    environment:\n      POSTGRES_USER: gitea\n      POSTGRES_PASSWORD: ${DB_PASSWORD:-SuperSecureProductionSecretPassword_2026}\n      POSTGRES_DB: gitea\n    volumes:\n      - \/srv\/gitea\/postgres:\/var\/lib\/postgresql\/data\n    networks:\n      - gitea_internal\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U gitea -d gitea\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"50m\"\n        max-file: \"3\"\n    deploy:\n      resources:\n        limits:\n          cpus: '2.00'\n          memory: 2048M\n\n  cache:\n    image: redis:7-alpine\n    container_name: gitea_cache\n    restart: always\n    command: redis-server --requirepass ${REDIS_PASSWORD:-SuperSecureRedisSecretToken_2026} --maxmemory 512mb --maxmemory-policy volatile-lru\n    volumes:\n      - \/srv\/gitea\/redis:\/data\n    networks:\n      - gitea_internal\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"-a\", \"${REDIS_PASSWORD:-SuperSecureRedisSecretToken_2026}\", \"ping\"]\n      interval: 10s\n      timeout: 5s\n      retries: 3\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"20m\"\n        max-file: \"2\"\n    deploy:\n      resources:\n        limits:\n          cpus: '1.00'\n          memory: 768M\n\n  server:\n    image: gitea\/gitea:1.22-rootless\n    container_name: gitea_app\n    restart: always\n    depends_on:\n      db:\n        condition: service_healthy\n      cache:\n        condition: service_healthy\n    networks:\n      - gitea_internal\n    environment:\n      - USER_UID=1000\n      - USER_GID=1000\n      - GITEA__database__DB_TYPE=postgres\n      - GITEA__database__HOST=db:5432\n      - GITEA__database__NAME=gitea\n      - GITEA__database__USER=gitea\n      - GITEA__database__PASSWD=${DB_PASSWORD:-SuperSecureProductionSecretPassword_2026}\n    volumes:\n      - \/srv\/gitea\/data:\/var\/lib\/gitea\n      - \/srv\/gitea\/config:\/etc\/gitea\n      - \/etc\/timezone:\/etc\/timezone:ro\n      - \/etc\/localtime:\/etc\/localtime:ro\n    ports:\n      - \"127.0.0.1:3000:3000\"\n      - \"127.0.0.1:2222:2222\"\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"100m\"\n        max-file: \"5\"\n    deploy:\n      resources:\n        limits:\n          cpus: '2.00'\n          memory: 2048M<\/code><\/pre>\n<h2>Standardizing Port 22 SSH Passthrough<\/h2>\n<p>By default, running Gitea inside Docker forces Git SSH traffic onto an awkward secondary port, such as <code>2222<\/code>. This impairs developer experience, requiring team members to configure custom SSH ports in remote clone URLs (e.g. <code>ssh:\/\/git@git.domain.com:2222\/org\/repo.git<\/code>) or populate local <code>~\/.ssh\/config<\/code> files.<\/p>\n<p>To deliver an enterprise developer experience, configure the host operating system&#8217;s OpenSSH daemon to route incoming <code>git@<\/code> connections directly into Gitea on standard port 22, while retaining regular port 22 access for systems administrators.<\/p>\n<p>Create a dedicated routing wrapper script at <code>\/usr\/local\/bin\/gitea-shell<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>#!\/bin\/sh\n# \/usr\/local\/bin\/gitea-shell\n# Transparent Host-to-Container SSH Routing Proxy for Gitea\n\nssh -q \\\n  -p 2222 \\\n  -i \/home\/git\/.ssh\/id_rsa \\\n  -o StrictHostKeyChecking=no \\\n  -o UserKnownHostsFile=\/dev\/null \\\n  git@127.0.0.1 \\\n  \"SSH_ORIGINAL_COMMAND=\\\"$SSH_ORIGINAL_COMMAND\\\" $0 $@\"<\/code><\/pre>\n<p>Set appropriate execution and ownership flags on the wrapper script:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo chmod +x \/usr\/local\/bin\/gitea-shell\nsudo chown root:root \/usr\/local\/bin\/gitea-shell<\/code><\/pre>\n<p>Next, configure host OpenSSH in <code>\/etc\/ssh\/sshd_config.d\/gitea.conf<\/code> to enforce shell containment for the <code>git<\/code> user:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/ssh\/sshd_config.d\/gitea.conf\nMatch User git\n    AuthorizedKeysFile \/srv\/gitea\/data\/git\/.ssh\/authorized_keys\n    ForceCommand \/usr\/local\/bin\/gitea-shell\n    PasswordAuthentication no\n    PubkeyAuthentication yes\n    X11Forwarding no\n    AllowTcpForwarding no\n    AllowAgentForwarding no<\/code><\/pre>\n<p>Reload host SSH daemon to activate standard port 22 Git access: <code>sudo systemctl reload sshd<\/code>.<\/p>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Security Hardening:<\/strong> Never mount the host Docker socket (<code>\/var\/run\/docker.sock<\/code>) directly into the Gitea container unless you are deploying Gitea Actions runners in rootless DinD (Docker-in-Docker) mode. Giving any web application direct access to the Docker socket allows complete root escalation over the host node.<\/p>\n<\/blockquote>\n<h2>Hardening the Production app.ini Configuration<\/h2>\n<p>Gitea maintains its persistent operational parameters inside <code>custom\/conf\/app.ini<\/code>. The following production configuration enforces Redis session persistence, optimizes PostgreSQL connection pooling, establishes strict repository quota boundaries, and locks down installer attack surfaces.<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>; \/srv\/gitea\/config\/conf\/app.ini\n; High-Throughput Production Settings for Gitea\n\nAPP_NAME = Enterprise Gitea Git Forge\nRUN_USER = git\nWORK_PATH = \/var\/lib\/gitea\nRUN_MODE = prod\n\n[server]\nPROTOCOL = http\nDOMAIN = git.example.com\nROOT_URL = https:\/\/git.example.com\/\nHTTP_ADDR = 0.0.0.0\nHTTP_PORT = 3000\nDISABLE_SSH = false\nSSH_PORT = 22\nSSH_LISTEN_PORT = 2222\nLFS_START_SERVER = true\nLFS_MAX_FILE_SIZE = 10485760000 ; 10GB LFS file ceiling\nOFFLINE_MODE = false\n\n[database]\nDB_TYPE = postgres\nHOST = db:5432\nNAME = gitea\nUSER = gitea\nPASSWD = SuperSecureProductionSecretPassword_2026\nSCHEMA = public\nSSL_MODE = disable\nCHARSET = utf8\nMAX_OPEN_CONNS = 50\nMAX_IDLE_CONNS = 15\nCONN_MAX_LIFETIME = 15m\n\n[cache]\nADAPTER = redis\nHOST = network=tcp,addr=cache:6379,password=SuperSecureRedisSecretToken_2026,db=0\n\n[queue]\nTYPE = redis\nCONN_STR = network=tcp,addr=cache:6379,password=SuperSecureRedisSecretToken_2026,db=1\n\n[session]\nPROVIDER = redis\nPROVIDER_CONFIG = network=tcp,addr=cache:6379,password=SuperSecureRedisSecretToken_2026,db=2\n\n[security]\nINSTALL_LOCK = true\nSECRET_KEY = 5cf8d743a4e69b22a01948d3c907131e5f884210\nREVERSE_PROXY_LIMIT = 1\nREVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.1,172.16.0.0\/12\nDISABLE_GIT_HOOKS = true\n\n[repository]\nMAX_CREATION_LIMIT = 500\nENABLE_PUSH_CREATE_USER = true\nENABLE_PUSH_CREATE_ORG = true\nDEFAULT_BRANCH = main<\/code><\/pre>\n<h2>TLS Termination &amp; Reverse Proxy Configuration (Nginx)<\/h2>\n<p>Placing Gitea behind an Nginx reverse proxy ensures secure TLS 1.3 termination, enables HTTP\/2 multiplexing, and accommodates massive Git LFS packfile uploads without request timeouts.<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/nginx\/conf.d\/gitea.conf\nupstream gitea_backend {\n    server 127.0.0.1:3000;\n    keepalive 32;\n}\n\nserver {\n    listen 80;\n    listen [::]:80;\n    server_name git.example.com;\n    return 301 https:\/\/$host$request_uri;\n}\n\nserver {\n    listen 443 ssl http2;\n    listen [::]:443 ssl http2;\n    server_name git.example.com;\n\n    ssl_certificate \/etc\/letsencrypt\/live\/git.example.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/git.example.com\/privkey.pem;\n    ssl_protocols TLSv1.2 TLSv1.3;\n    ssl_ciphers HIGH:!aNULL:!MD5;\n    ssl_prefer_server_ciphers on;\n\n    # Accommodate large Git commits, binary packages, and LFS chunks\n    client_max_body_size 512M;\n\n    # Extended proxy timeouts for prolonged packfile generation\n    proxy_connect_timeout 600;\n    proxy_send_timeout 600;\n    proxy_read_timeout 600;\n    send_timeout 600;\n\n    location \/ {\n        proxy_pass http:\/\/gitea_backend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_http_version 1.1;\n        proxy_set_header Connection \"\";\n    }\n}<\/code><\/pre>\n<h2>Performance Benchmarks: Default vs. Tuned Production Topology<\/h2>\n<p>Benchmarking an untuned Gitea container against our tuned three-tier architecture reveals dramatic latency improvements and concurrency ceilings under high-load synthetic Git operations.<\/p>\n<figure class=\"wp-block-table is-style-regular\">\n<table style=\"width:100%;border-collapse:collapse;margin:24px 0;font-size:15px;text-align:left\">\n<thead style=\"background:#001b41;color:#ffffff\">\n<tr>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Feature \/ Metric<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Standard \/ Default<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Tuned \/ Production<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Database Backend<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">SQLite3 (File Lock Bottleneck)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">PostgreSQL 16 (Connection Pool)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Idle Memory Footprint<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">~250 MB (Unconstrained)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">~110 MB (Go GC Tuned)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">SSH Architecture<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Container Port 2222 (Non-standard)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Host Port 22 Passthrough (AuthorizedKeys)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Concurrent Git Clones<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">10-15 Concurrency Limit<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">200+ Requests \/ sec via NVMe<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Backup &amp; Recovery RPO<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Manual tarball dumps<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Automated WAL-G \/ S3 Sync (15m RPO)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Automated Disaster Recovery and Snapshot Script<\/h2>\n<p>A reliable self-hosting architecture requires zero-downtime, consistent backup procedures. Because Gitea relies on relational database records alongside raw Git repository directories on the filesystem, executing simple file copies while the database is actively writing risks corrupted, inconsistent backups.<\/p>\n<p>Deploy the following automated backup script at <code>\/usr\/local\/bin\/backup-gitea.sh<\/code> to create synchronized, atomic snapshots:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>#!\/usr\/bin\/env bash\n# \/usr\/local\/bin\/backup-gitea.sh\n# Production Automated Backup Automation for Gitea + PostgreSQL Stack\nset -euo pipefail\n\nBACKUP_DIR=\"\/var\/backups\/gitea\"\nTIMESTAMP=$(date +\"%Y%m%d_%H%M%S\")\nRETENTION_DAYS=14\n\nmkdir -p \"${BACKUP_DIR}\"\n\necho \"[$(date)] Starting Gitea internal archive generation...\"\ndocker exec -u git gitea_app gitea dump -c \/etc\/gitea\/app.ini -f \/tmp\/gitea-dump-${TIMESTAMP}.zip\n\necho \"[$(date)] Copying archive to local backup storage...\"\ndocker cp gitea_app:\/tmp\/gitea-dump-${TIMESTAMP}.zip \"${BACKUP_DIR}\/\"\ndocker exec -u git gitea_app rm -f \/tmp\/gitea-dump-${TIMESTAMP}.zip\n\necho \"[$(date)] Executing PostgreSQL relational database dump...\"\ndocker exec gitea_db pg_dump -U gitea -d gitea -F c -b -v -f \/tmp\/gitea_db_${TIMESTAMP}.dump\ndocker cp gitea_db:\/tmp\/gitea_db_${TIMESTAMP}.dump \"${BACKUP_DIR}\/\"\ndocker exec gitea_db rm -f \/tmp\/gitea_db_${TIMESTAMP}.dump\n\necho \"[$(date)] Pruning backups older than ${RETENTION_DAYS} days...\"\nfind \"${BACKUP_DIR}\" -name \"gitea*\" -type f -mtime +${RETENTION_DAYS} -delete\n\necho \"[$(date)] Gitea backup cycle completed successfully.\"<\/code><\/pre>\n<p>Schedule this script in root crontab to execute every night at 02:00 AM: <code>0 2 * * * \/usr\/local\/bin\/backup-gitea.sh &gt;&gt; \/var\/log\/gitea-backup.log 2&gt;&amp;1<\/code>.<\/p>\n<h2>Scaling Infrastructure &amp; Storage I\/O Optimization<\/h2>\n<p>When scaling beyond a handful of private developers into high-throughput continuous deployment environments, disk I\/O latency becomes the primary bottleneck. Standard virtual servers with shared mechanical disks or throttled network-attached storage experience severe I\/O stalls during repository garbage collection and concurrent Git operations. For mission-critical production instances where performance consistency and cost predictability are paramount, deploying on <a href=\"https:\/\/merahost.org\" target=\"_blank\" rel=\"noopener\">MeraHost Enterprise Cloud<\/a> provides dedicated enterprise NVMe storage arrays, LiteSpeed optimization, and a transparent pricing policy with zero renewal price hikes.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">How much RAM and CPU does a self-hosted Gitea server require in production?<\/summary>\n<p style=\"margin-top:10px;color:#444\">In contrast to resource-heavy alternatives like GitLab, a base Gitea container runs efficiently on 512 MB of RAM and a single vCPU core for small development teams (10-25 developers). For enterprise teams with 100+ active contributors running continuous CI\/CD pipelines and large Git LFS operations, allocating 2 vCPUs, 2 GB to 4 GB of RAM, and high-IOPS NVMe storage ensures sub-second response times.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">What is the difference between Gitea and Forgejo?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Forgejo is a hard community fork of Gitea initiated in late 2022 to preserve a strictly community-governed, non-commercial software model. Both platforms maintain substantial architectural and API compatibility, running on the identical Go foundation. The Docker deployment configurations, PostgreSQL integrations, and SSH passthrough scripts outlined in this guide operate identically across both Gitea and Forgejo images.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Why should I choose PostgreSQL over SQLite for Gitea in Docker?<\/summary>\n<p style=\"margin-top:10px;color:#444\">SQLite uses file-level locking mechanisms. When multiple developers push code simultaneously or automated CI systems poll webhooks, SQLite locks the database file, creating serialization queues and timeout errors. PostgreSQL 16 supports concurrent row-level locking, robust connection pooling, point-in-time recovery (PITR), and reliable atomic backups without halting Gitea service operations.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">How do I configure Git LFS (Large File Storage) with self-hosted Gitea?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Gitea includes a built-in Git LFS server enabled via <code>LFS_START_SERVER = true<\/code> under the <code>[server]<\/code> block in <code>app.ini<\/code>. Large binary files are stored in <code>\/var\/lib\/gitea\/data\/lfs<\/code> by default, or they can be routed directly to S3-compatible object storage by configuring the <code>[lfs]<\/code> section with bucket credentials, preventing disk bloat on your primary server.<\/p>\n<\/details>\n<div class=\"wp-block-group has-background\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:8px;padding:32px;margin:40px 0;text-align:center\">\n<h3 style=\"color:#001b41;margin-top:0;font-size:24px;font-weight:700\">Deploy Enterprise-Grade Production Infrastructure<\/h3>\n<p style=\"color:#444;font-size:16px;line-height:1.6;max-width:680px;margin:12px auto 24px auto\">Need guaranteed performance with zero price hikes? Host mission-critical workloads on <strong style=\"color:#001b41\">MeraHost<\/strong> with pure Enterprise NVMe, LiteSpeed Web Server, and Same Renewal Price, Always (starting at \u20b999\/mo).<\/p>\n<div class=\"wp-block-buttons\" style=\"display:flex;gap:16px;justify-content:center;flex-wrap:wrap\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"https:\/\/merahost.org\" style=\"background:#001b41;color:#ffffff;font-weight:700;padding:12px 28px;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\" target=\"_blank\" rel=\"noopener\">Explore MeraHost NVMe Cloud &rarr;<\/a><\/div>\n<div class=\"wp-block-button is-style-outline\"><a class=\"wp-block-button__link\" href=\"https:\/\/cpanelfree.com\" style=\"background:transparent;color:#001b41;font-weight:600;padding:12px 24px;border:2px solid #001b41;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\">Deploy Free Staging on CpanelFree<\/a><\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to deploy a high-performance self-hosted Gitea server with Docker and PostgreSQL. Includes production configs, SSH passthrough, and tuning.<\/p>\n","protected":false},"author":1,"featured_media":4914,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[220],"tags":[57,177,87,221,101],"class_list":["post-4915","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-self-hosting","tag-almalinux","tag-databases-performance","tag-devops","tag-self-hosting","tag-sysadmin"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4915","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=4915"}],"version-history":[{"count":0,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/4915\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/4914"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=4915"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=4915"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=4915"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}