Almost Free Serverless Private Git Repository
This translated content is generated by a large language model.
As a developer, we always need a place to store our personal code that isn't convenient to make public. GitHub's private repositories are great, but they lack that sense of "being in control". On the other hand, self-hosting Gitea or GitLab is too heavy, requiring a virtual machine running continuously for a single web page. Recently, I explored a cost-saving solution: using the free tier of cloud providers' Serverless offerings, paired with Alpine Linux + Dropbear, to build an image that automatically shuts down during idle time (zero cost), creating a private Git server that can scale up at any time.
This solution uses the SSH protocol to access a private Git repository and a container image to run this Git service. It only processes requests when SSH receives one, thereby fully utilizing the cloud provider's scale-to-zero function during idle times.
Solution Highlights
- Extremely low cost: Compute resources utilize the monthly free tiers of Azure or other cloud providers (such as Google Cloud Run); storage uses Azure Files, which consumes very little space for repositories that consist almost entirely of source code.
- Data safety: Data is kept as persistent storage, so even if the container crashes, the data will not be lost.
- Auto-scaling: Cloud providers often offer a Scale to Zero feature. When no one is using it, the replica count is 0 (no cost); once SSH connects, it wakes up instantly.
Tech Stack Selection: Why Alpine + Dropbear?
When building this image, I didn't choose the common Ubuntu + OpenSSH combination, but opted for Alpine + Dropbear instead. This was not to show off, but a calculated decision to save resources in Serverless scenarios.
Alpine Linux is the top choice for cold starts and embedded systems. In Azure Container Apps, after setting the scale-to-zero, when you initiate a request, the platform needs to pull the container from scratch—this is the "cold start" process. Ubuntu's base image is typically 70MB+, while Alpine is only 5MB. A smaller size means faster image pulling and decompression. In cold-start scenarios, Alpine's minimalist initialization process saves us several seconds of waiting time.
Dropbear: The soul of embedded systems OpenSSH is the industry standard, but it is too "bloated" for an ultra-lightweight micro-container. Dropbear is an SSH server designed specifically for embedded systems (like routers running OpenWrt). Its memory footprint is extremely low (usually just a few MBs), whereas each connection process in OpenSSH consumes much more. Dropbear's server and key tools are packed into a tiny binary, reducing system call overhead. This container doesn't need complex PAM authentication, GSSAPI, or X11 forwarding. It only needs a secure tunnel to run
git-upload-pack, and Dropbear provides exactly what is needed.
Summary: Choosing Alpine + Dropbear is meant to achieve good response speeds even under the lowest resource limits, while leaving memory for Git's garbage collection to prevent OOM (Out Of Memory) errors. The packaged image size is around 10MB, and it can run normally under 100 MB or even less memory. This image is also perfect for running on home routers or simple embedded devices, serving as an almost zero-burden Git repository.
Container Image Configuration
I have published an image on my self-hosted Forgejo package registry. The current version is 1.0.0, and the source code is published in the corresponding Forgejo repository. For more details on self-hosted Forgejo, please refer to this introduction.
Source code: https://forgejo.goba.ip-dynamic.org/gobro/simple-git-server
Image address: forgejo.goba.ip-dynamic.org/gobro/simple-git-server:latest
Cosign public key: https://forgejo.goba.ip-dynamic.org/gobro/simple-git-server/raw/branch/main/cosign.pub
Cosign public key fingerprint: SHA256:c52fc605506bb1b8f2e39d57ac7810935311af6feab400b904e3083b951b8314
Below, I will introduce how to use this image through a docker-compose file and a .env environment configuration file.
name: simple-git
services:
simple-git:
image: ghcr.io/kwfcfc/git-server:latest
container_name: git-test
ports:
- "2222:22" # Map to your desired port
volumes:
- git_repo:/home/git
env_file: ".env"
volumes:
git_repo: This is the environment variable file.
# Base64 encoded ed25519 key
ED25519_KEY=
# Base64 encoded rsa key
RSA_KEY=
# you can also mount the authorized_keys into
# /auth/authorized_keys file inside container
AUTHORIZED_KEYS='ssh-ed25519 change-me'
REPO_NAMES='repo1 repo2.git'
GIT_PORT=22 When starting this image, you need to pass an ED25519 and RSA private key encoded in base64, so that every time the container restarts, it won't cause local errors due to host key changes. This private key format is different from OpenSSH, so you need to use Dropbear's tools to convert it. If you have the dropbear utility and base64 encoding tools locally, you can also generate them using the following commands:
dropbearkey -t ed25519 -f dropbear_ed25519_host_key
dropbearkey -t rsa -f dropbear_rsa_host_key
base64 -i dropbear_ed25519_host_key
base64 -i dropbear_rsa_host_key The AUTHORIZED_KEYS in the environment variables is your local SSH public key, usually ~/.ssh/id_ed25519.pub or ~/.ssh/id_rsa.pub. If you need multiple public keys, you can also write these public keys one per line into a file and mount it to the /auth/authorized_keys file. REPO_NAMES are the names of the repositories you want to create on this server: upon startup, the container will create these repositories under /home/git/. Finally, GIT_PORT is the default port exposed by Dropbear. If you are using docker-compose or certain cloud providers, you can map it to other ports.
How to Connect
If your Git server is running successfully, you can test it with the following command (replace GIT_PORT, id_ed25519, and your_ip with your actual configuration):
ssh -T -p GIT_PORT -i ~/.ssh/id_ed25519 git@your.ip If the configuration is successful, you should see:
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access. Cloud Provider Deployment Process (Taking Azure Container Apps as an Example)
During deployment, a few key settings determine its success or failure.
Mount Storage (Volumes): Create an Azure File Share. Mount it in Container Apps with the path set to
/home/git. This ensures your code repositories are persistent.Network (Ingress): You need to create a network interface first, and then let the container application use this address.
Traffic: TCP
Target Port: 22
Exposed Port: 22 (or other; my recommendation is to choose an uncommon high port to avoid being scanned)
Ultimate Cost Saving: Scale to Zero
This is the essence of Serverless. We need to set up rules: turn on when someone connects via SSH, and shut down when no one is connected.
Frequently Asked Questions (Q&A)
Q: There are always connections from IP 100.100.x.x in the logs that disconnect immediately. Is it a hacker?
A: No. This is Azure Ingress's health probe. Because we enabled TCP Ingress, the load balancer needs to confirm that the port is open. You can safely ignore these "Exit before auth" logs.
Q: How long does a cold start take?
A: About 15-30 seconds. When you push code for the first time in the morning, it might hang for a moment. Once the container spins up, subsequent operations will be very fast.
Q: Is it really free every month?
A: Compute cost: The free tier includes 180,000 vCPU-seconds. For a 0.25 vCPU specification, this is equivalent to 200 hours of active usage per month. For personal repositories, this is almost impossible to exhaust. As for storage cost, Azure Files is pay-as-you-go. Storing 1GB of code costs about $0.06 per month.
Conclusion
With this solution, for less than the price of a cup of coffee, we have a highly available, auto-backed, globally accessible private Git repository on Azure. In addition, cloud providers like Google Cloud Run offer substantial free tiers that allow this image to run at a very low cost or even completely free. You can also try using such a simple Git repository on your home embedded devices.
评论系统尚未配置。请在 .env 中填写 giscus 所需的环境变量。