chore: init pogoo-atlas deploy repo (site + api + db, isolated)

This commit is contained in:
Kantin Petit 2026-08-08 17:00:41 +02:00
commit f7ca90571c
11 changed files with 246 additions and 0 deletions

2
.env.example Normal file
View file

@ -0,0 +1,2 @@
# In Portainer, set DB_PASSWORD as a stack environment variable (do NOT commit .env).
DB_PASSWORD=change-me-strong

3
.gitattributes vendored Normal file
View file

@ -0,0 +1,3 @@
* text=auto eol=lf
*.png binary
*.ico binary

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env

26
README.md Normal file
View file

@ -0,0 +1,26 @@
# pogoo-atlas — repo `git.pogoo.app/kpetit/pogoo-atlas`
Self-contained deploy repo for **atlas.pogoo.app** (Pogoo · Atlas). Fully isolated: own frontend + API + Postgres. Portainer-git friendly.
```
pogoo-atlas/
├── docker-compose.yml # web (./site) + api + db, all isolated
└── site/ # index.html, Dockerfile, nginx.conf, favicons (stone-blue accent)
```
## Deploy (Portainer-git, auto)
1. Push to Forgejo: `git.pogoo.app/kpetit/pogoo-atlas`.
2. Portainer → Stacks → Add → **Git repository**
- URL: `https://git.pogoo.app/kpetit/pogoo-atlas.git`
- Compose path: `docker-compose.yml`
- Env var: `DB_PASSWORD` (set in the stack, not committed)
- **Automatic updates + webhook: ON**
3. Forgejo repo → Settings → Webhooks → paste the Portainer webhook URL.
## Prereqs
- Edge stack up, `edge` network exists.
- DNS: `atlas.pogoo.app` → server IP.
## Notes
- `api` is a `traefik/whoami` placeholder — swap for the real Atlas backend.
- `db` (Postgres) stays on the private `atlas_net`, never on `edge`. Its data is this product's own.

63
docker-compose.yml Normal file
View file

@ -0,0 +1,63 @@
# atlas.pogoo.app — full product stack, self-contained repo for Portainer-git auto-deploy.
# Fully ISOLATED: own frontend + own API + own DB. Nothing shared with other products.
name: pogoo-atlas
services:
web:
build:
context: ./site
image: pogoo/atlas-web:latest
restart: unless-stopped
networks: [edge]
labels:
- "traefik.enable=true"
- "traefik.http.routers.atlas.rule=Host(`atlas.pogoo.app`)"
- "traefik.http.routers.atlas.entrypoints=websecure"
- "traefik.http.routers.atlas.tls.certresolver=le"
- "traefik.http.routers.atlas.middlewares=security-headers@file"
- "traefik.http.services.atlas.loadbalancer.server.port=80"
api:
# Placeholder — swap traefik/whoami for the real Atlas backend image.
image: traefik/whoami:latest
restart: unless-stopped
environment:
- DATABASE_URL=postgres://atlas:${DB_PASSWORD}@db:5432/atlas
networks: [edge, atlas_net]
depends_on:
db:
condition: service_healthy
labels:
- "traefik.enable=true"
- "traefik.http.routers.atlas-api.rule=Host(`atlas.pogoo.app`) && PathPrefix(`/api`)"
- "traefik.http.routers.atlas-api.entrypoints=websecure"
- "traefik.http.routers.atlas-api.tls.certresolver=le"
- "traefik.http.routers.atlas-api.priority=100"
- "traefik.http.routers.atlas-api.middlewares=atlas-strip@docker,ratelimit-std@file"
- "traefik.http.middlewares.atlas-strip.stripprefix.prefixes=/api"
- "traefik.http.services.atlas-api.loadbalancer.server.port=80"
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
- POSTGRES_USER=atlas
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=atlas
volumes:
- atlas_pgdata:/var/lib/postgresql/data
networks: [atlas_net] # private — never on edge
healthcheck:
test: ["CMD-SHELL", "pg_isready -U atlas"]
interval: 10s
timeout: 5s
retries: 5
networks:
edge:
external: true
atlas_net:
driver: bridge
volumes:
atlas_pgdata:

5
site/Dockerfile Normal file
View file

@ -0,0 +1,5 @@
# Immutable static image for atlas.pogoo.app (product frontend).
FROM nginx:1.27-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY . /usr/share/nginx/html
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost/ >/dev/null || exit 1

BIN
site/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
site/favicon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
site/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

135
site/index.html Normal file
View file

@ -0,0 +1,135 @@
<!doctype html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Atlas — Know where everything is · by Pogoo</title>
<meta name="description" content="Atlas, by Pogoo. A living map of your infrastructure and services — know where everything is, at a glance."/>
<link rel="icon" href="favicon.ico" sizes="any"/>
<link rel="icon" href="favicon-32.png" type="image/png" sizes="32x32"/>
<link rel="apple-touch-icon" href="apple-touch-icon.png"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Instrument+Sans:wght@400;500;600&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet"/>
<style>
/* Atlas INHERITS the Pogoo spine (type, grid, radius, motion, neutrals) and OWNS its accent: stone blue. */
:root{
--ink:#1A1714;--sand-50:#FAF7F2;--sand-100:#F2ECE3;--sand-200:#E0D6C8;--clay-600:#6B6357;--ink-800:#2A251F;--ink-900:#1A1714;--ink-950:#14110E;
--accent:#4E7C8C;--accent-strong:#3E6675;--accent-bright:#6FA3B4; /* the only line that differs from the mother brand */
--bg:var(--sand-50);--surface:#fff;--surface-2:var(--sand-100);--border:var(--sand-200);--text:var(--ink-900);--muted:var(--clay-600);--on-accent:#fff;
--fd:'Space Grotesk','Segoe UI',system-ui,sans-serif;--fb:'Instrument Sans','Inter',system-ui,sans-serif;--fm:'Space Mono',ui-monospace,monospace;
}
:root[data-theme="dark"]{--bg:var(--ink-950);--surface:var(--ink-900);--surface-2:var(--ink-800);--border:#332C24;--text:#F5F0E8;--muted:#A69D8D;--accent:#6FA3B4;--accent-strong:#4E7C8C;--on-accent:var(--ink-950)}
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:var(--ink-950);--surface:var(--ink-900);--surface-2:var(--ink-800);--border:#332C24;--text:#F5F0E8;--muted:#A69D8D;--accent:#6FA3B4;--accent-strong:#4E7C8C;--on-accent:var(--ink-950)}}
*{box-sizing:border-box;margin:0}
body{background:var(--bg);color:var(--text);font-family:var(--fb);line-height:1.6;-webkit-font-smoothing:antialiased}
.wrap{max-width:1120px;margin:0 auto;padding:0 26px}
h1,h2,h3,.d{font-family:var(--fd);letter-spacing:-.03em;text-wrap:balance}.mono{font-family:var(--fm)}
nav{position:sticky;top:0;z-index:20;backdrop-filter:blur(12px);background:color-mix(in srgb,var(--bg) 80%,transparent);border-bottom:1px solid var(--border)}
nav .wrap{display:flex;align-items:center;justify-content:space-between;height:70px}
/* co-brand lockup: pogoo (mother) · atlas (product) */
.lock{display:flex;align-items:center;gap:9px;font-family:var(--fd);font-weight:600;font-size:20px;letter-spacing:-.03em}
.lock svg{width:28px;height:28px}
.lock .mother{color:var(--muted);font-weight:500}.lock .dot{color:var(--accent)}
.nlinks{display:flex;gap:24px;align-items:center;font-size:15px}.nlinks a{color:var(--muted);text-decoration:none}.nlinks a:hover{color:var(--text)}
.tbtn{font-family:var(--fm);font-size:12px;background:var(--surface);border:1px solid var(--border);color:var(--muted);padding:7px 12px;border-radius:99px;cursor:pointer}
.hero{padding:96px 0 40px;text-align:center;display:flex;flex-direction:column;align-items:center}
.eyebrow{font-family:var(--fm);font-size:12px;text-transform:uppercase;letter-spacing:.14em;color:var(--muted)}
.hero h1{font-size:clamp(2.6rem,7vw,4.8rem);font-weight:700;line-height:1;margin-top:16px;max-width:16ch}
.hero .sub{margin-top:22px;font-size:clamp(1.05rem,2vw,1.3rem);color:var(--muted);max-width:46ch}
.cta{margin-top:30px;display:flex;gap:16px;align-items:center}
.btn{background:var(--accent-strong);color:#fff;font-family:var(--fd);font-weight:600;padding:14px 24px;border-radius:14px;text-decoration:none;transition:transform .14s}.btn:hover{transform:translateY(-1px)}
.ghost{font-family:var(--fm);color:var(--muted);text-decoration:none;font-size:14px}.ghost:hover{color:var(--accent)}
/* product UI mock */
.mock{margin:56px auto 0;max-width:920px;border:1px solid var(--border);border-radius:20px;overflow:hidden;box-shadow:0 24px 60px rgba(26,23,20,.14);background:var(--surface)}
.mockbar{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--border);background:var(--surface-2)}
.mockbar i{width:11px;height:11px;border-radius:99px;background:var(--sand-200)}
.mockbar .url{margin-left:12px;font-family:var(--fm);font-size:12px;color:var(--muted)}
.mockbody{display:grid;grid-template-columns:180px 1fr;min-height:320px}
.side{border-right:1px solid var(--border);padding:18px;display:flex;flex-direction:column;gap:6px;background:var(--surface-2)}
.side .it{font-family:var(--fm);font-size:12px;color:var(--muted);padding:7px 10px;border-radius:8px}
.side .it.on{background:var(--accent);color:#fff}
.main{padding:22px}
.main h3{font-size:1.1rem;font-weight:600}
.map{margin-top:16px;display:grid;grid-template-columns:repeat(4,1fr);gap:10px}
.node{border:1px solid var(--border);border-radius:12px;padding:12px;position:relative;background:var(--bg)}
.node .n{font-family:var(--fm);font-size:11px}.node .s{font-family:var(--fm);font-size:10px;margin-top:6px}
.node.ok .s{color:var(--accent)}.node.warn .s{color:#D9A441}.node.down .s{color:#C24A38}
.node::before{content:"";position:absolute;top:10px;right:10px;width:7px;height:7px;border-radius:99px}
.node.ok::before{background:var(--accent)}.node.warn::before{background:#D9A441}.node.down::before{background:#C24A38}
section{padding:64px 0}
h2{font-size:clamp(1.7rem,3.6vw,2.3rem);font-weight:700;margin-top:8px}
.feats{margin-top:30px;display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:18px}
.feat{border-top:2px solid var(--accent);padding-top:16px}.feat b{font-family:var(--fd);font-size:1.1rem;display:block}.feat p{color:var(--muted);font-size:14px;margin-top:6px}
.family{border:1px solid var(--border);border-radius:20px;padding:30px;display:flex;gap:20px;align-items:center;flex-wrap:wrap;background:var(--surface)}
.family svg{width:40px;height:40px}
.family p{color:var(--muted);max-width:52ch}.family a{color:var(--accent);text-decoration:none;font-family:var(--fm);font-size:14px}
footer{background:var(--ink-950);color:var(--sand-50);padding:48px 0}
footer .wrap{display:flex;justify-content:space-between;flex-wrap:wrap;gap:20px;align-items:center}
footer .lock{color:var(--sand-50)}footer small{font-family:var(--fm);color:#A69D8D}
@media(max-width:760px){.mockbody{grid-template-columns:1fr}.side{flex-direction:row;overflow:auto;border-right:0;border-bottom:1px solid var(--border)}.map{grid-template-columns:repeat(2,1fr)}.nlinks a:not(.pill){display:none}}
</style>
</head>
<body>
<nav><div class="wrap">
<a class="lock" href="https://pogoo.app" style="text-decoration:none;color:inherit">
<svg viewBox="0 0 100 100"><defs><mask id="n"><rect width="100" height="100" fill="#fff"/><rect x="45" y="-8" width="12" height="42" rx="6" transform="rotate(45 50 50)" fill="#000"/></mask></defs><g fill="var(--accent)"><path fill-rule="evenodd" d="M50 11a39 39 0 1 0 0 78 39 39 0 0 0 0-78Zm0 21a18 18 0 1 1 0 36 18 18 0 0 1 0-36Z" mask="url(#n)"/><circle cx="50" cy="50" r="8.5"/></g></svg>
<span><span class="mother">pogoo</span> <span class="dot">·</span> atlas</span>
</a>
<span class="nlinks"><a href="#feats">Features</a><a href="#">Docs</a><a href="#">Pricing</a><a href="#" class="pill"><button class="tbtn" id="tt"></button></a></span>
</div></nav>
<header class="hero wrap">
<div class="eyebrow">Pogoo · Atlas</div>
<h1>Know where everything is.</h1>
<p class="sub">Atlas draws a living map of your infrastructure and services — every node, its state, its owner — so nothing hides.</p>
<div class="cta"><a class="btn" href="#">Start mapping</a><a class="ghost" href="#feats">see how it works </a></div>
<div class="mock">
<div class="mockbar"><i></i><i></i><i></i><span class="url">atlas.pogoo.app/map</span></div>
<div class="mockbody">
<aside class="side">
<div class="it on">▤ Map</div><div class="it">◇ Services</div><div class="it">◷ Timeline</div><div class="it">⚑ Owners</div><div class="it">⚙ Settings</div>
</aside>
<div class="main">
<h3>Production · 12 nodes</h3>
<div class="map">
<div class="node ok"><div class="n">api-gateway</div><div class="s">healthy · 24ms</div></div>
<div class="node ok"><div class="n">auth</div><div class="s">healthy · 11ms</div></div>
<div class="node warn"><div class="n">workers</div><div class="s">degraded · 210ms</div></div>
<div class="node ok"><div class="n">postgres</div><div class="s">healthy · 3ms</div></div>
<div class="node ok"><div class="n">cache</div><div class="s">healthy · 1ms</div></div>
<div class="node down"><div class="n">mailer</div><div class="s">down · 0</div></div>
<div class="node ok"><div class="n">cdn</div><div class="s">healthy · 8ms</div></div>
<div class="node ok"><div class="n">search</div><div class="s">healthy · 30ms</div></div>
</div>
</div>
</div>
</div>
</header>
<section id="feats" class="wrap">
<div class="eyebrow">What Atlas does</div>
<h2>A map you can trust at a glance.</h2>
<div class="feats">
<div class="feat"><b>Auto-discovery</b><p>Point Atlas at your cloud and repos. It finds services and draws the graph — no manual diagrams.</p></div>
<div class="feat"><b>Live state</b><p>Health, latency and ownership on every node. Green, amber, red — the way you already think.</p></div>
<div class="feat"><b>Who owns what</b><p>Every service has a name attached. No more "whose is this?" at 3am.</p></div>
<div class="feat"><b>Time travel</b><p>Scrub the timeline to see how the map looked before an incident.</p></div>
</div>
</section>
<section class="wrap">
<div class="family">
<svg viewBox="0 0 100 100"><defs><mask id="fm"><rect width="100" height="100" fill="#fff"/><rect x="45" y="-8" width="12" height="42" rx="6" transform="rotate(45 50 50)" fill="#000"/></mask></defs><g fill="var(--accent)"><path fill-rule="evenodd" d="M50 11a39 39 0 1 0 0 78 39 39 0 0 0 0-78Zm0 21a18 18 0 1 1 0 36 18 18 0 0 1 0-36Z" mask="url(#fm)"/><circle cx="50" cy="50" r="8.5"/></g></svg>
<p><b>Atlas is part of Pogoo</b> — a house of good tools. Same spine, same craft, its own colour. <a href="https://pogoo.app">See the whole house →</a></p>
</div>
</section>
<footer><div class="wrap">
<span class="lock"><svg viewBox="0 0 100 100"><defs><mask id="ff"><rect width="100" height="100" fill="#fff"/><rect x="45" y="-8" width="12" height="42" rx="6" transform="rotate(45 50 50)" fill="#000"/></mask></defs><g fill="var(--accent-bright)"><path fill-rule="evenodd" d="M50 11a39 39 0 1 0 0 78 39 39 0 0 0 0-78Zm0 21a18 18 0 1 1 0 36 18 18 0 0 1 0-36Z" mask="url(#ff)"/><circle cx="50" cy="50" r="8.5"/></g></svg><span><span style="color:#A69D8D;font-weight:500">pogoo</span> · atlas</span></span>
<small>atlas.pogoo.app — a Pogoo product</small>
</div></footer>
<script>document.getElementById('tt').addEventListener('click',function(e){e.preventDefault();var r=document.documentElement,c=r.getAttribute('data-theme')||(matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light');r.setAttribute('data-theme',c==='dark'?'light':'dark');});</script>
</body>
</html>

11
site/nginx.conf Normal file
View file

@ -0,0 +1,11 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location ~* \.(?:png|ico|svg|woff2?|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location / { try_files $uri $uri/ /index.html; }
}