Infrastructure as Code · Web Platform
Portal PRJAAS.
One server, many apps — a self-service web hosting platform,
rebuilt on demand from code. Any tech stack, behind one domain, secured by default.
Why we built it
The goals
- Ship apps fast — onboard a new web app with one file, no bespoke server work.
- Any tech stack — .NET, Python, Java, Node, static — all behind one domain.
- Secure by default — automatic HTTPS, secrets never stored in git.
- Reproducible & disposable — rebuild the entire server from code, on demand.
- Low friction, low cost — one VM, shared services, no per-app infrastructure.
At a glance
What Portal PRJAAS is
- Ansible-only Infrastructure as Code, run by hand from a workstation.
- Apps run in Docker containers behind Traefik (reverse proxy + automatic TLS).
- Shared platform services: PostgreSQL, Redis, MinIO.
- App images pulled from a private Nexus registry (offline fallback available).
- Code & IaC managed in Azure DevOps — the shared XD_Commons project.
- Target: a single Ubuntu 24.04 Azure VM — rebuildable from scratch.
How it fits together
The architecture
proxy
Traefik — the only container on ports 80/443. Automatic Let's Encrypt TLS, HTTP→HTTPS, path routing.
platform
PostgreSQL · Redis · MinIO — shared data services, internal only, never exposed.
apps
Your web apps — wired to Traefik by labels, one path each.
edge network — Traefik ↔ apps
platform network — apps ↔ datastores
Routing — Host(fqdn) && PathPrefix(/app)
How it fits together
Architecture — the full picture
The core idea
One declaration file per app
- Each app is a single YAML file — apps/<app>.yml.
- Declares: image, path, port, env, secrets, health, shared services, pull policy.
- Ansible renders the compose file, Traefik labels and .env, provisions the database, and refreshes the homepage.
- Add an app = drop one file + run one command.
ansible-playbook site.yml --tags apps
Bring your own stack
App building alternatives
The platform is stack-agnostic — it only asks for a small container contract:
serve plain HTTP on one port, honor a path prefix, expose a health endpoint, ship curl, build linux/amd64.
.NET
ASP.NET Core 8 reference app — build, containerize, deploy.
/dotnetsample
Python
FastAPI twin — same shape, different language.
/pythonsample
Java
Spring Boot with Microsoft Entra ID login (SSO).
/login
Node.js
Powers the AI database chatbot — our first production app.
/dbchat
From code to live
How onboarding works
- BuildMulti-stage Dockerfile for your app.
- PublishPush to Nexus — or side-load while the firewall is pending.
- DeclareAdd apps/<app>.yml (+ secrets in the vault).
- DeployRun the apps playbook — one command.
- LiveHTTPS, routing, health & homepage — automatic.
Included out of the box
Batteries included
Shared data
Postgres (DB + user per app), Redis, MinIO — auto-wired via shared_services. Plus a read-only reporting user and a documented sample database.
Authentication
Per-app Microsoft Entra ID login with SSO — a documented, repeatable convention.
Backups
Nightly Postgres dumps and volume archives, retained automatically.
Branded homepage
Every app appears on an Inetum-branded landing page — generated automatically.
Live · First production app · Node.js
AI Chatbot — explore your data
Our first non-demo application, live at /dbchat — brought from its own
XD_Commons repository and onboarded onto the platform unchanged. A conversational assistant that lets people
query the databases in plain language, no SQL required.
Ask in natural language
"How many orders shipped last week?" — the assistant turns questions into queries and answers.
Safe by design
Runs through the cluster-wide read-only Postgres role — it can read data, never change it.
Schema-aware
Uses the documented tables & column comments (e.g. the flowershop sample DB) for accurate answers.
Status today
Where we are
- Live: Traefik with production TLS, platform datastores, nightly backups.
- First production app: dbchat — the AI database chatbot, onboarded from its own repo.
- Reference samples: hello · dotnetsample · pythonsample · javaloginsample.
- Self-contained onboarding skill documents the whole contract — reusable beyond this project.
- Pending: Nexus firewall opening — offline deploy workaround already in place.
See it live
The running platform
Mini guide
Onboard an app in 5 steps
Two actors: the app developer builds the app and
publishes its image to Nexus; the platform manager declares
it and deploys. The platform just needs a container that serves plain HTTP on one port,
binds 0.0.0.0, honors a path prefix, and exposes a health endpoint.
- DevMeet the contractHTTP on a port, a /healthz endpoint, ship curl, build linux/amd64.
- DevBuild the imageMulti-stage Dockerfile — copy a sample to start.
- DevPublishPush the image to Nexus, or side-load with docker save / load.
- PlatformDeclareAdd apps/<app>.yml + put secrets in the vault.
- PlatformDeployRun the apps playbook — HTTPS, routing & homepage are automatic.
Dev app developer — owns the app & image (its own XD_Commons repo)
Platform platform manager — owns the IaC repo & the server
Full contract & field reference:
util/skills/prjaas-webapp-onboarding/SKILL.md
🧠
Onboarding skill for OpenCode. The full container contract & field
reference ships as a portable
SKILL.md —
download it from the homepage.
Install: save it to
~/.config/opencode/skills/prjaas-webapp-onboarding/SKILL.md
(or
.opencode/skills/… in your repo), then restart OpenCode.
The toolchain behind it
Development support tools
Two internal servers provide everything the teams need to plan, build,
scan and ship — all on our own infrastructure.
Azure DevOps Server
Planning & source control. Jira-like work tracking (boards, backlogs,
sprints) and the Git repositories — the shared XD_Commons project.
boards · repos · pull requests
XDCommonsrv
Build, quality & artifacts. Jenkins CI/CD pipelines,
SonarQube code-quality analysis, and a Nexus repository with the
Docker artifacts registry enabled.
Jenkins · SonarQube · Nexus (Docker)
Your move
Have an app?
Let's host it.
Bring a container that meets the contract — any stack — and we onboard it in minutes.
Copy a sample, write one YAML file, ship it.
Everything lives in Azure DevOps —
XD_Commons: dev.azure.com/GfiPortugalDev/XD_Commons