- Python 56%
- Makefile 44%
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| test.py | ||
Poorman's Homestack
A lightweight self-hosted infrastructure stack managed via a single interactive make command. Runs entirely on Docker and is designed for low-end machines.
Includes databases, storage, a git server with CI runners, a local AI interface, and supporting services (more soon to come!! )— all on a single machine.
Goal
Minimal setup. Minimal overhead. Everything in one place.
Resource Usage (Idle)
- CPU: <10% total
- ~0.2 core on 2-core systems (~10%)
- ~0.2 core on 4-core systems (~5%)
- RAM: <2.5 GB total
Stack
| Service | Image | Default Port | Purpose |
|---|---|---|---|
| PostgreSQL | postgres:17.4-alpine |
9100 | Relational database |
| MongoDB | mongo:8.0.4 |
9101 | Document database |
| Elasticsearch | elasticsearch:8.17.0 |
9102 | Search engine |
| MinIO | minio/minio |
9103 / 9104 | S3-compatible object storage |
| Forgejo | forgejo:14 |
9203 / 2222 | Self-hosted Git + CI |
| Forgejo Runner | forgejo/runner:9.0.3 |
— | CI job executor |
| Open WebUI | open-webui:main |
9202 | Local AI chat interface (Ollama frontend) |
| File Browser | filebrowser:v2.30.0 |
9200 | Web-based file manager |
| Portainer | portainer-ce:2.39.1 |
9201 | Docker management UI |
| IT Tools | corentinth/it-tools:latest |
9204 | Developer utility web interface |
| Transmute | ghcr.io/transmute-app/transmute:latest |
9205 | Low-code AI automation platform |
Prerequisites
Install these before anything else.
Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in after this
Make
# Debian/Ubuntu
sudo apt install make
# Alpine
sudo apk add make
Gum
Gum powers the interactive CLI menu.
# Debian/Ubuntu
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
sudo apt update && sudo apt install gum
# macOS
brew install gum
Ollama (optional)
Required only if you want Open WebUI to work. Install on the host machine (not in Docker):
curl -fsSL https://ollama.com/install.sh | sh
ollama pull llama3 # or any model you prefer
Setup
1. Clone the repo
git clone <repo-url> homestack
cd homestack
2. Create your .env file
cp .env.example .env
Open .env and fill in every value. Do not leave any value as the example default in production.
# PostgreSQL
POSTGRES_USER=mypostgresuser
POSTGRES_PASSWORD=mypostgrespassword
POSTGRES_DB=mypostgresdb
POSTGRES_PORT=9100
# MongoDB
MONGO_USER=mymongouser
MONGO_PASSWORD=mymongopassword
MONGO_DB=mymongodb
MONGO_PORT=9101
# Elasticsearch
ELASTIC_PASSWORD=myelasticpassword
ELASTIC_PORT=9102
# MinIO
MINIO_ROOT_USER=myminiouser
MINIO_ROOT_PASSWORD=myminiopassword
MINIO_API_PORT=9103
MINIO_CONSOLE_PORT=9104
# File Browser
FILEBROWSER_PORT=9200
FILEBROWSER_SRV=/srv
PUID=1000
PGID=1000
# Open WebUI
OPEN_WEBUI_PORT=9202
WEBUI_ADMIN_EMAIL=you@example.com
# Portainer
PORTAINER_PORT=9201
PORTAINER_ADMIN_PASSWORD_HASH=<bcrypt hash>
# ITTOOLS
ITTOOLS_PORT=9204
# TRANSMUTE
TRANSMUTE_PORT=9205
# Forgejo
FORGEJO_HTTP_PORT=9203
FORGEJO_SSH_PORT=2222
FORGEJO_DB_NAME=myforgejodb
FORGEJO_DB_USER=myforgejouser
FORGEJO_DB_PASSWORD=myforgejopassword
FORGEJO_DOMAIN=forgejo.yourdomain.com
FORGEJO_ADMIN_USER=youradminuser
FORGEJO_ADMIN_PASSWORD=yourpassword
FORGEJO_ADMIN_EMAIL=you@example.com
Important notes on .env:
- Do not wrap values in quotes — the sourcing mechanism reads them literally and quotes become part of the value
- Passwords with spaces work fine without quotes
PORTAINER_ADMIN_PASSWORD_HASHmust be a bcrypt hash — generate with:docker run --rm httpd:2.4-alpine htpasswd -nbB admin yourpassword | cut -d: -f2FORGEJO_DOMAINis used for SSH clone URLs and the ROOT_URL — set it to your actual domain or your server's local hostname/IP
3. Start everything
make
Select Start services → all from the menu.
First Run: Forgejo Setup
When starting for the first time, Forgejo requires a one-time web UI setup:
- Open
http://<your-server>:<FORGEJO_HTTP_PORT>in your browser - The setup wizard will appear — the database fields will already be filled from your env vars, do not change them
- Fill in your admin account details — use the same username and password as
FORGEJO_ADMIN_USERandFORGEJO_ADMIN_PASSWORDin your.env - Click Install Forgejo
- Wait for the page to load (can take 30–60 seconds on first boot)
- Log in to confirm it worked
- Go back to the terminal and confirm Yes when asked to register the runner
The runner registration fetches a token from the Forgejo API and registers the CI runner automatically. After this, ./data/forgejo-runner/.runner will exist and the runner daemon will start on all future boots without any manual steps.
Usage
make
The interactive menu offers:
- Start services — start all or individual services
- Stop services — stop all or individual services
- Initialize services — re-run initialization (Forgejo DB + runner, MinIO)
- View logs — tail logs for any service
- Clean up — stop everything and delete all volumes (destructive)
Data & Volumes
Named volumes (managed by Docker, wiped on make clean)
postgres_data— PostgreSQL datamongo_data— MongoDB dataesdata— Elasticsearch indicesminio_data— MinIO object storagefilebrowser_db— File Browser databaseportainer_data— Portainer stateopenwebui_data— Open WebUI data
Bind mounts (host directories, wiped on make clean)
./data/forgejo— Forgejo repos, config, attachments./data/forgejo-runner— Runner registration state (.runnerfile)
make clean removes both named volumes and bind mount directories and recreates them with correct permissions.
Networking
All services run on a single Docker bridge network (172.20.0.0/16). Services communicate with each other using their container names as hostnames (e.g. Forgejo connects to Postgres at pg:5432).
No service is exposed to the internet by default — all ports are bound to 0.0.0.0 on the host. Put a reverse proxy (Caddy, Nginx, Traefik) in front if you want HTTPS or external access.
Diagnostics
Check what's running
docker ps
Tail logs for a specific service
make # → View logs → select service
# or directly:
docker logs <container> -f --tail 50
Check a healthcheck
docker inspect <container> --format '{{.State.Health.Status}}'
Postgres — connect and inspect
docker exec -it pg psql -U <POSTGRES_USER> -d <POSTGRES_DB>
\du # list users
\l # list databases
Elasticsearch — cluster health
curl -u elastic:<ELASTIC_PASSWORD> http://localhost:9102/_cluster/health
MinIO — liveness
curl -f http://localhost:<MINIO_API_PORT>/minio/health/live && echo "OK"
MongoDB — ping
curl http://localhost:<MONGO_PORT>/
# Expected: "It looks like you are trying to access MongoDB over HTTP..."
# This means it is running correctly.
Forgejo runner not starting
docker logs forgejo-runner --tail 30
If you see connection refused to Forgejo — the runner registered before Forgejo was ready. Re-run:
make init-forgejo-runner
If you see permission denied writing .runner:
sudo chown -R 1000:1000 ./data/forgejo-runner
make init-forgejo-runner
Common Issues
Forgejo goes straight to login instead of setup wizard
The ./data/forgejo directory has data from a previous run. Wipe it:
make clean
Forgejo shows a ROOT_URL warning
You are accessing Forgejo via a different URL than FORGEJO_DOMAIN. This is cosmetic if you are accessing locally — it will not break core functionality. Set FORGEJO_DOMAIN to your server's local hostname or IP if you are not using a public domain.
Runner crash-loops with connection refused
Forgejo was not fully up when the runner tried to connect. The depends_on healthcheck should prevent this, but if it happens:
docker restart forgejo-runner
make clean fails with permission denied
Forgejo creates root-owned files inside ./data/forgejo. The Makefile handles this with sudo rm -rf — if it still fails, run manually:
sudo rm -rf ./data/forgejo ./data/forgejo-runner
Elasticsearch stays YELLOW Normal during startup while shards are being allocated. It resolves to GREEN within 30–60 seconds. Only investigate if it stays YELLOW after 2 minutes:
curl -u elastic:<ELASTIC_PASSWORD> http://localhost:9102/_cluster/health?pretty
MongoDB deprecated parameter warnings in logs These are warnings about config parameter names that will be removed in a future MongoDB version. They do not affect functionality. Update the parameter names in your MongoDB config when you have time.
MongoDB swappiness warning
MongoDB recommends setting vm.swappiness to 0 or 1 on the host:
sudo sysctl vm.swappiness=1
# To persist across reboots:
echo 'vm.swappiness=1' | sudo tee -a /etc/sysctl.conf
Things Not To Do
- Do not run
docker compose up -ddirectly — always usemake. The Makefile handles initialization order, database setup, and runner registration thatdocker composealone does not. - Do not delete
./data/forgejo-runner/.runnermanually while the stack is running — the runner will lose its registration and crash-loop. - Do not change
FORGEJO_DOMAINafter the first run without also updating the Forgejoapp.iniinside./data/forgejo/gitea/conf/app.ini— the two must match. - Do not wrap
.envvalues in quotes — they are sourced directly by the shell and quotes become part of the value. - Do not expose Elasticsearch or MongoDB ports publicly without authentication configured — both have no network-level auth by default beyond what the application layer provides.
Resetting Everything
This is fully destructive — all data will be lost:
make clean
To start fresh after a clean:
make
# → Start services → all
# → Complete Forgejo web UI setup in browser
# → Confirm runner registration in terminal