Skip to content

Repository files navigation

devops.local

English · Русский

Private LAN platform for one Debian/Ubuntu box: HTTPS portal, message queue, cache, databases, Docker registry, CI runner, Maven and Debian repos. Ansible + Docker Compose, no cloud.

Host: devops.local. Portal: https://devops.local/ (English by default, EN/RU toggle). Trust the CA once: http://devops.local/ca.crt.

Service portal

RabbitMQPostgreSQL · Adminer
RabbitMQAdminer
MongoDB · expressMaven · Reposilite
Mongo ExpressReposilite

Debian APT

Web UIs sit behind nginx HTTPS. AMQP / MQTT / Redis / Postgres / Mongo / Elasticsearch stay on native ports.

URL
Portalhttps://devops.local/
CAhttp://devops.local/ca.crt
Portainerhttps://devops.local/portainer/ (admin)
Registry UIhttps://devops.local/registry/
Registry APIhttps://devops.local/v2/ · docker login devops.local
RabbitMQhttps://devops.local/rabbitmq/ · AMQP :5672 · MQTT :1883 · WS :15675
Redis Commanderhttps://devops.local/redis/ · Redis :6379
PostgreSQLhttps://devops.local/postgres/ · :5432
MongoDBhttps://devops.local/mongo/ · :27017
Elasticsearchhttps://devops.local/elastic/ · :9200
Mavenhttps://devops.local/maven/
Debian APThttps://devops.local/debian/
Gitea MCPhttp://devops.local:8080/mcp
Local Giteahttp://devops.local:3000/ (optional, GITEA_ENABLED=true)

Docs: Maven / APT · databases · TLS / docker login · Gitea.

docker login devops.local
docker tag myimg devops.local/myimg
docker push devops.local/myimg

Stacks

StackOn the hostWhat it runs
devops/opt/compose/devopsrunner, MCP, Maven, Debian, registry, Portainer, TLS/CA; local Gitea if GITEA_ENABLED=true
aiml/opt/compose/aimlOpen WebUI + Speaches (STT/TTS, CPU) · optional Ollama
monitoring/opt/compose/monitoringGrafana + Prometheus + Loki / Alloy (container logs)
middleware/opt/compose/middlewarenginx portal :80:443, RabbitMQ (+MQTT), Redis, Postgres, Mongo, ES

Each service is optional (make setup toggles / .env*_ENABLED). Compose profiles follow those flags.

Runner / MCP always use gitea_instance_url (existing Synology Gitea) — they are not rewritten to :3000. See docs/gitea.md.

Data lives on the host (bind mounts, not named volumes):

PathContents
/opt/compose/tlsPrivate CA + nginx server cert (owned by devops)
/opt/compose/middleware/data/rabbitmqRabbitMQ
/opt/compose/middleware/data/redisRedis AOF/RDB
/opt/compose/middleware/data/postgresPostgreSQL
/opt/compose/middleware/data/mongoMongoDB
/opt/compose/middleware/data/elasticsearchElasticsearch
/opt/compose/devops/data/runneract_runner state
/opt/compose/devops/data/mavenReposilite
/opt/compose/devops/data/debianreprepro pool / dists / incoming
/opt/compose/devops/data/giteaLocal Gitea (SQLite + repos)
/opt/compose/devops/data/debian-gpgAPT signing key
/opt/compose/devops/data/registryRegistry blobs
/opt/compose/devops/data/portainerPortainer
/opt/compose/aiml/data/ollamaOllama models (optional)
/opt/compose/aiml/data/speachesSpeaches STT/TTS models
/opt/compose/aiml/data/openwebuiOpen WebUI
/opt/compose/monitoring/data/prometheusPrometheus TSDB
/opt/compose/monitoring/data/lokiLoki chunks / index
/opt/compose/monitoring/data/alloyAlloy state
/opt/compose/monitoring/data/grafanaGrafana

First deploy: Ansible creates host bind-mount dirs under /opt/compose/{devops,aiml,monitoring,middleware}/data.

Deploy

Ansible runs in WSL. On Windows, ansible.ps1 wraps wsl make. Native Windows is not a supported control node.

Prerequisites: WSL with make + python3-venv, SSH key to darvik@devops.local, sudo on the target.

# WSL
sudo apt update && sudo apt install -y make python3-venv python3-pip
cd /mnt/d/Projects/devops/docker

Do not edit tracked inventory for secrets. make setup writes a gitignored .env (component toggles, credentials, optional CA paths); deploy targets read it via .env.yml + inventory/local.ini.

make setup # components + secrets + optional bring-your-own CA
make ping
make docker # Docker CE + Compose plugin
make devops # TLS + registry/portainer/runner/… (before middleware)
make aiml # Open WebUI + Speaches (+ Ollama if enabled)
make monitoring # Grafana + Prometheus + Loki/Alloy
make middleware # portal + data services
make deploy # all of the above (site.yml)

Windows:

.\ansible.ps1 setup
.\ansible.ps1 ping
.\ansible.ps1 docker
.\ansible.ps1 devops
.\ansible.ps1 aiml
.\ansible.ps1 monitoring
.\ansible.ps1 middleware
.\ansible.ps1 deploy

Other targets: make install, make check, make version, make clean, make help.

The Makefile uses project .venv and sets ANSIBLE_CONFIG / ANSIBLE_ROLES_PATH / ANSIBLE_INVENTORY (needed on world-writable /mnt/d/...).

Playbook issues a private CA + server cert, installs the CA into OS trust and /etc/docker/certs.d/ on devops.local, and does not use insecure-registries. LAN clients need the same CA (docs/registry-tls.md).

Portainer: user admin, password from .env (PORTAINER_PASSWORD, min 12 chars). --admin-password-file only seeds the first init. If login fails after a previous deploy, set PORTAINER_FORCE_REINIT=true once, make devops, then set it back to false (wipes Portainer data).

Grafana: https://devops.local/grafana/ — admin user/password from .env (GRAFANA_ADMIN_*). Logging (local Docker + remote hosts / bare-metal via Alloy agents): docs/logging.md.
media-vn (Synology) → devops.local step-by-step (RU):docs/logging-media-vn.md
(agent = Docker container + shared volume / syslog; no host docker.sock).

make devops issues TLS under /opt/compose/tls and brings up registry/portainer/runner/….

New portal card: add portal_services in inventory/group_vars/middleware.yml and, if needed, location = /status/... in stacks/middleware/nginx/default.conf.j2. Bilingual copy: portal_i18n + title / description{ en, ru }.

Refresh screenshots (needs .env and a reachable portal; portal is forced to English):

npm install
npx playwright install chromium
PORTAL_URL=https://devops.local npm run screenshots

Cursor MCP:

{
"mcpServers": {
"gitea": {
"url": "http://devops.local:8080/mcp",
"headers": {
"Authorization": "Bearer <gitea_mcp_access_token>"
}
}
}
}

Variables

Credentials and host/URL overrides: .env (make setup). Defaults live in inventory/group_vars/ — do not put secrets there.

VariableDefaultDescription
gitea_enabledfalseLocal Gitea. .env: GITEA_ENABLED
runner_enabled / mcp_enabled / maven_enabled / debian_enabledtrueDevOps toggles
ollama_enabledfalseLocal LLM API (OLLAMA_ENABLED=true to enable)
speaches_enabled / openwebui_enabledtrueVoice STT/TTS + chat UI
prometheus_enabled / grafana_enabled / loki_enabledtrueMetrics + Grafana UI + Loki/Alloy logs
ollama_modelsgemma2:2b,qwen3:1.7bPulled when Ollama enabled
speaches_tts_defaultkokoroDefault TTS in Open WebUI (kokoro or piper; both preloaded)
registry_enabled / portainer_enabledtrueRegistry + Portainer (devops stack)
rabbitmq_enabled / redis_enabled / postgres_enabled / mongo_enabled / elastic_enabledtrueMiddleware toggles
portal_langenPortal default language (en / ru); UI toggle still works
tls_ca_source_crt / tls_ca_source_key``Optional local CA paths (from make setup)
gitea_instance_urlSynology URLRunner/MCP target. Not rewritten to local :3000
gitea_http_port / gitea_ssh_port3000 / 2222Local Gitea HTTP / SSH
gitea_root_urlhttp://devops.local:3000/Local server ROOT_URL only
gitea_runner_namedevops.localRunner name in Gitea
gitea_runner_labelsdevops.local:docker://catthehacker/ubuntu:act-latestRunner labels
gitea_mcp_port8080Host port for gitea-mcp HTTP
maven_http_port8083Reposilite on localhost (nginx /maven/)
debian_http_port8084Debian www on localhost (nginx /debian/)
gitea_runner_force_reregisterfalseDelete .runner and register again
middleware_http_port80Nginx HTTP → HTTPS redirect + /ca.crt
middleware_https_port443Nginx portal, UIs, registry /v2/
tls_enabledtruePrivate CA + server cert for nginx
tls_extra_dns / tls_extra_ips[]Extra SANs on the server cert
rabbitmq_amqp_port5672RabbitMQ AMQP
rabbitmq_mqtt_port1883RabbitMQ MQTT (same plugins as iot-platform-go)
rabbitmq_mqtt_ws_port15675RabbitMQ MQTT over WebSocket
redis_port6379Redis
postgres_port5432PostgreSQL
mongo_port27017MongoDB
elastic_http_port9200Elasticsearch HTTP
elastic_java_opts-Xms512m -Xmx512mES heap
compose_root/opt/composeRemote directory for stacks

Do not commit .env, .env.yml, or inventory/local.ini.

Adding a stack

  1. Add stacks/<name>/docker-compose.yml and .env.j2
  2. Add an inventory group and playbook that applies compose_stack with compose_stack_name: <name>

Troubleshooting

bcrypt / password_hash / __about__

Old passlib + bcrypt 5.x is incompatible. This repo hashes via scripts/bcrypt_hash.py. Reinstall venv deps:

make install
make middleware

No module named 'ansible.module_utils.six.moves'

Broken system Ansible. Use the Makefile venv only:

make clean
make install
make deploy

Gitea RunnerService/Declare 500

The runner keeps a hidden /opt/compose/devops/data/runner/.runner after first register. If the token was reset or the runner was removed in Gitea UI, Declare returns 500. Registration env vars are ignored while .runner exists.

ssh darvik@devops.local
sudo docker compose -f /opt/compose/devops/docker-compose.yml stop devops-runner
sudo rm -f /opt/compose/devops/data/runner/.runner
sudo docker compose -f /opt/compose/devops/docker-compose.yml up -d devops-runner

Or set GITEA_RUNNER_FORCE_REREGISTER=true once and make devops, then set it back to false. Use a fresh token from Gitea → Site Administration → Actions → Runners.

stacks not starting

ssh darvik@devops.local
sudo docker compose -f /opt/compose/devops/docker-compose.yml ps
sudo docker compose -f /opt/compose/devops/docker-compose.yml logs
sudo docker compose -f /opt/compose/middleware/docker-compose.yml ps
sudo docker compose -f /opt/compose/middleware/docker-compose.yml logs

Releases

Packages

Contributors

Languages