@codions/portta 0.0.0-develop.147.sha-1859ad1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/skills/portta-workflows/SKILL.md +280 -0
- package/dist/assets/workflows/bake-off.workflow.js +171 -0
- package/dist/assets/workflows/code-review.workflow.js +179 -0
- package/dist/assets/workflows/deep-research.workflow.js +222 -0
- package/dist/assets/workflows/multi-provider-review.workflow.js +168 -0
- package/dist/assets/workflows/provider-debate.workflow.js +140 -0
- package/dist/assets/workflows/second-opinion.workflow.js +175 -0
- package/dist/cli.js +129793 -0
- package/dist/documentation.json +1 -0
- package/dist/host.js +73741 -0
- package/dist/runtime/.env.example +460 -0
- package/dist/runtime/VERSION +1 -0
- package/dist/runtime/config/traefik/dynamic/auth.example.yaml.disabled +24 -0
- package/dist/runtime/config/traefik/dynamic/middlewares.yaml +35 -0
- package/dist/runtime/config/traefik/dynamic/tcp.yaml +23 -0
- package/dist/runtime/docker/compose/attach/host.yaml +24 -0
- package/dist/runtime/docker/compose/attach/tailscale.yaml +70 -0
- package/dist/runtime/docker/compose/compose.yaml +214 -0
- package/dist/runtime/docker/compose/features/auth-build.yaml +19 -0
- package/dist/runtime/docker/compose/features/auth-dev.yaml +20 -0
- package/dist/runtime/docker/compose/features/cloudflare-tunnel.yaml +69 -0
- package/dist/runtime/docker/compose/features/dashboard-tailscale.yaml +21 -0
- package/dist/runtime/docker/compose/features/dashboard.yaml +21 -0
- package/dist/runtime/docker/compose/features/db.yaml +63 -0
- package/dist/runtime/docker/compose/features/panel-domain.yaml +42 -0
- package/dist/runtime/docker/compose/features/panel-public.yaml +47 -0
- package/dist/runtime/docker/compose/features/panel-webhook.yaml +46 -0
- package/dist/runtime/docker/compose/features/taskflow.yaml +38 -0
- package/dist/runtime/docker/compose/features/tcp-tailscale.yaml +30 -0
- package/dist/runtime/docker/compose/features/tcp.yaml +40 -0
- package/dist/runtime/docker/compose/features/web-bind.yaml +19 -0
- package/dist/runtime/docker/compose/features/web-build.yaml +21 -0
- package/dist/runtime/docker/compose/features/web-dev.yaml +54 -0
- package/dist/runtime/docker/compose/features/web-vpn.yaml +24 -0
- package/dist/runtime/docker/compose/features/web.yaml +283 -0
- package/dist/runtime/docker/compose/profiles/local-tls.yaml +23 -0
- package/dist/runtime/docker/compose/profiles/local.yaml +15 -0
- package/dist/runtime/docker/compose/profiles/public.yaml +19 -0
- package/dist/runtime/docker/compose/profiles/remote-tls-dns.yaml +26 -0
- package/dist/runtime/docker/compose/profiles/remote-tls-http.yaml +32 -0
- package/dist/runtime/docker/compose/profiles/remote-tls.yaml +31 -0
- package/dist/runtime/docker/compose/profiles/remote.yaml +23 -0
- package/dist/runtime/docker/images/README.md +12 -0
- package/dist/runtime/docker/images/apply/Dockerfile +45 -0
- package/dist/runtime/docker/images/sandbox/Dockerfile +79 -0
- package/dist/runtime/docker/images/sandbox/README.md +109 -0
- package/dist/runtime/docker/images/sandbox/VERSION +1 -0
- package/dist/runtime/docker/images/sandbox/agents/claude-code.sh +18 -0
- package/dist/runtime/docker/images/sandbox/agents/codex.sh +4 -0
- package/dist/runtime/docker/images/sandbox/agents/opencode.sh +4 -0
- package/dist/runtime/docker/images/sandbox/agents/pi.sh +4 -0
- package/dist/runtime/docker/images/sandbox/scripts/entrypoint.sh +8 -0
- package/dist/runtime/docker/images/sandbox/scripts/generate-manifest.mjs +81 -0
- package/dist/runtime/docker/images/sandbox/scripts/install-tools.sh +46 -0
- package/dist/runtime/docker/images/sandbox/scripts/verify-image.sh +18 -0
- package/dist/runtime/docker/images/toolbox/Dockerfile +38 -0
- package/dist/runtime/scripts/lib/runner-exec.sh +189 -0
- package/dist/runtime/templates/README.md +43 -0
- package/dist/runtime/templates/overlays/01-single-web.yaml +23 -0
- package/dist/runtime/templates/overlays/02-web-api.yaml +30 -0
- package/dist/runtime/templates/overlays/03-web-api-postgres.yaml +33 -0
- package/dist/runtime/templates/overlays/04-web-api-postgres-redis.yaml +30 -0
- package/dist/runtime/templates/overlays/05-multiple-apis.yaml +37 -0
- package/dist/runtime/templates/overlays/06-monorepo.yaml +63 -0
- package/dist/runtime/templates/overlays/07-worktree.env +47 -0
- package/dist/runtime/templates/overlays/08-nonstandard-port.yaml +41 -0
- package/dist/runtime/templates/overlays/09-tcp-routing.yaml +70 -0
- package/dist/runtime/templates/overlays/10-mailpit.yaml +24 -0
- package/dist/runtime/templates/overlays/11-rustfs.yaml +25 -0
- package/dist/runtime/templates/project/PORTTA.md +107 -0
- package/dist/supervisor.js +23928 -0
- package/package.json +63 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Templates
|
|
2
|
+
|
|
3
|
+
Reference overlays for the shapes a project usually has. They are **references
|
|
4
|
+
to copy from**, not runtime that projects depend on: nothing here is included,
|
|
5
|
+
mounted or extended by a consumer project. Copy the closest one, adjust the
|
|
6
|
+
service names and ports, and it is yours.
|
|
7
|
+
|
|
8
|
+
`portta env init <path>` generates the same thing from a project's actual
|
|
9
|
+
Compose file, which is usually faster.
|
|
10
|
+
|
|
11
|
+
| Template | Shape |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [`01-single-web.yaml`](overlays/01-single-web.yaml) | one HTTP service |
|
|
14
|
+
| [`02-web-api.yaml`](overlays/02-web-api.yaml) | web + API |
|
|
15
|
+
| [`03-web-api-postgres.yaml`](overlays/03-web-api-postgres.yaml) | web + API + a database that stays private |
|
|
16
|
+
| [`04-web-api-postgres-redis.yaml`](overlays/04-web-api-postgres-redis.yaml) | the common full stack |
|
|
17
|
+
| [`05-multiple-apis.yaml`](overlays/05-multiple-apis.yaml) | several APIs behind one namespace |
|
|
18
|
+
| [`06-monorepo.yaml`](overlays/06-monorepo.yaml) | `apps/` and `services/` in one repository |
|
|
19
|
+
| [`07-worktree.env`](overlays/07-worktree.env) | a second copy of a project, in parallel |
|
|
20
|
+
| [`08-nonstandard-port.yaml`](overlays/08-nonstandard-port.yaml) | an HTTP service on an unusual internal port |
|
|
21
|
+
| [`09-tcp-routing.yaml`](overlays/09-tcp-routing.yaml) | opt a datastore into hostname TCP routing |
|
|
22
|
+
| [`10-mailpit.yaml`](overlays/10-mailpit.yaml) | Mailpit UI on the gateway (SMTP stays private) |
|
|
23
|
+
| [`11-rustfs.yaml`](overlays/11-rustfs.yaml) | RustFS console on the gateway (S3 API stays private) |
|
|
24
|
+
|
|
25
|
+
## The two rules every template follows
|
|
26
|
+
|
|
27
|
+
**Labels are in list form.** Compose interpolates `${VAR}` inside a list entry
|
|
28
|
+
but **not** inside a mapping key. Written as a map, the Traefik service name
|
|
29
|
+
keeps the literal `${COMPOSE_PROJECT_NAME}` and every worktree of the project
|
|
30
|
+
collapses onto one load balancer.
|
|
31
|
+
|
|
32
|
+
**Traefik service names carry the namespace.** Those names are flat across the
|
|
33
|
+
whole host. Two projects declaring a bare `web` are merged into a single load
|
|
34
|
+
balancer and start serving each other's traffic.
|
|
35
|
+
|
|
36
|
+
`portta doctor` reports both.
|
|
37
|
+
|
|
38
|
+
## For the consumer repository
|
|
39
|
+
|
|
40
|
+
[`project/PORTTA.md`](project/PORTTA.md) is a short page to copy into
|
|
41
|
+
the project itself: how to start it, its URLs, how to reach its database, how
|
|
42
|
+
to run a second copy. It deliberately does not restate the gateway's
|
|
43
|
+
documentation; this repository stays the single source of the rules.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# One HTTP service
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# docker compose -f compose.yaml -f compose.portta.yaml up -d
|
|
5
|
+
# -> http://<COMPOSE_PROJECT_NAME>-web.localhost
|
|
6
|
+
# ============================================================================
|
|
7
|
+
|
|
8
|
+
services:
|
|
9
|
+
web:
|
|
10
|
+
networks:
|
|
11
|
+
- default
|
|
12
|
+
- portta
|
|
13
|
+
labels:
|
|
14
|
+
- "traefik.enable=true"
|
|
15
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
16
|
+
# Only needed when the image's EXPOSE does not match the port the app
|
|
17
|
+
# actually listens on. It usually does not, so state it.
|
|
18
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-web.loadbalancer.server.port=3000"
|
|
19
|
+
|
|
20
|
+
networks:
|
|
21
|
+
portta:
|
|
22
|
+
external: true
|
|
23
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Web + API
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Both keep their internal ports. Other projects on this host use the same
|
|
5
|
+
# 3000 and 8000 at the same time, and nothing collides, because neither
|
|
6
|
+
# publishes a host port.
|
|
7
|
+
#
|
|
8
|
+
# -> http://<COMPOSE_PROJECT_NAME>-web.localhost
|
|
9
|
+
# -> http://<COMPOSE_PROJECT_NAME>-api.localhost
|
|
10
|
+
# ============================================================================
|
|
11
|
+
|
|
12
|
+
services:
|
|
13
|
+
web:
|
|
14
|
+
networks: [default, portta]
|
|
15
|
+
labels:
|
|
16
|
+
- "traefik.enable=true"
|
|
17
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
18
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-web.loadbalancer.server.port=3000"
|
|
19
|
+
|
|
20
|
+
api:
|
|
21
|
+
networks: [default, portta]
|
|
22
|
+
labels:
|
|
23
|
+
- "traefik.enable=true"
|
|
24
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
25
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api.loadbalancer.server.port=8000"
|
|
26
|
+
|
|
27
|
+
networks:
|
|
28
|
+
portta:
|
|
29
|
+
external: true
|
|
30
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Web + API + PostgreSQL
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Note what is NOT here: postgres. A database has no business on the shared
|
|
5
|
+
# network, since everything else on the host could reach it, and it does not
|
|
6
|
+
# need a host port either.
|
|
7
|
+
#
|
|
8
|
+
# The API reaches it as `postgres:5432` over the project's own network, exactly
|
|
9
|
+
# as before. A human reaches it on demand:
|
|
10
|
+
#
|
|
11
|
+
# portta access open --project <name> --service postgres
|
|
12
|
+
# -> 127.0.0.1:<free port>
|
|
13
|
+
# ============================================================================
|
|
14
|
+
|
|
15
|
+
services:
|
|
16
|
+
web:
|
|
17
|
+
networks: [default, portta]
|
|
18
|
+
labels:
|
|
19
|
+
- "traefik.enable=true"
|
|
20
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
21
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-web.loadbalancer.server.port=3000"
|
|
22
|
+
|
|
23
|
+
api:
|
|
24
|
+
networks: [default, portta]
|
|
25
|
+
labels:
|
|
26
|
+
- "traefik.enable=true"
|
|
27
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
28
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api.loadbalancer.server.port=8000"
|
|
29
|
+
|
|
30
|
+
networks:
|
|
31
|
+
portta:
|
|
32
|
+
external: true
|
|
33
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Web + API + PostgreSQL + Redis: the common full stack
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Identical to template 03: adding a cache changes nothing about the contract.
|
|
5
|
+
# Postgres and Redis both stay on the project's private network, both keep
|
|
6
|
+
# their standard ports, and both are reached with `portta access open`.
|
|
7
|
+
#
|
|
8
|
+
# Workers are absent too. A queue consumer serves no HTTP, so it has nothing to
|
|
9
|
+
# route; it stays on `default` and needs no changes at all.
|
|
10
|
+
# ============================================================================
|
|
11
|
+
|
|
12
|
+
services:
|
|
13
|
+
web:
|
|
14
|
+
networks: [default, portta]
|
|
15
|
+
labels:
|
|
16
|
+
- "traefik.enable=true"
|
|
17
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
18
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-web.loadbalancer.server.port=3000"
|
|
19
|
+
|
|
20
|
+
api:
|
|
21
|
+
networks: [default, portta]
|
|
22
|
+
labels:
|
|
23
|
+
- "traefik.enable=true"
|
|
24
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
25
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api.loadbalancer.server.port=8000"
|
|
26
|
+
|
|
27
|
+
networks:
|
|
28
|
+
portta:
|
|
29
|
+
external: true
|
|
30
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Several APIs in one project
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Each gets its own hostname from its own service name. They can all listen on
|
|
5
|
+
# 8000 internally; nothing needs renumbering.
|
|
6
|
+
#
|
|
7
|
+
# -> http://<COMPOSE_PROJECT_NAME>-api-auth.localhost
|
|
8
|
+
# -> http://<COMPOSE_PROJECT_NAME>-api-billing.localhost
|
|
9
|
+
# -> http://<COMPOSE_PROJECT_NAME>-api-search.localhost
|
|
10
|
+
# ============================================================================
|
|
11
|
+
|
|
12
|
+
services:
|
|
13
|
+
api-auth:
|
|
14
|
+
networks: [default, portta]
|
|
15
|
+
labels:
|
|
16
|
+
- "traefik.enable=true"
|
|
17
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
18
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api-auth.loadbalancer.server.port=8000"
|
|
19
|
+
|
|
20
|
+
api-billing:
|
|
21
|
+
networks: [default, portta]
|
|
22
|
+
labels:
|
|
23
|
+
- "traefik.enable=true"
|
|
24
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
25
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api-billing.loadbalancer.server.port=8000"
|
|
26
|
+
|
|
27
|
+
api-search:
|
|
28
|
+
networks: [default, portta]
|
|
29
|
+
labels:
|
|
30
|
+
- "traefik.enable=true"
|
|
31
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
32
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api-search.loadbalancer.server.port=8000"
|
|
33
|
+
|
|
34
|
+
networks:
|
|
35
|
+
portta:
|
|
36
|
+
external: true
|
|
37
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Monorepo: apps/ and services/ in one repository
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# A monorepo is one Compose project with more services in it. Nothing about the
|
|
5
|
+
# contract changes, and the gateway needs to know nothing about the layout.
|
|
6
|
+
#
|
|
7
|
+
# repo/
|
|
8
|
+
# apps/web/ -> web
|
|
9
|
+
# apps/admin/ -> admin
|
|
10
|
+
# services/api/ -> api
|
|
11
|
+
# services/worker/ -> worker (no HTTP; not routed)
|
|
12
|
+
# services/importer/ -> importer (no HTTP; not routed)
|
|
13
|
+
# compose.yaml
|
|
14
|
+
# compose.portta.yaml <- this file
|
|
15
|
+
#
|
|
16
|
+
# -> http://<COMPOSE_PROJECT_NAME>-web.localhost
|
|
17
|
+
# -> http://<COMPOSE_PROJECT_NAME>-admin.localhost
|
|
18
|
+
# -> http://<COMPOSE_PROJECT_NAME>-api.localhost
|
|
19
|
+
#
|
|
20
|
+
# Build contexts, Dockerfiles and volumes stay exactly where they are. The
|
|
21
|
+
# gateway never centralises any of that.
|
|
22
|
+
#
|
|
23
|
+
# Splitting one monorepo into several namespaces is possible: run separate
|
|
24
|
+
# Compose projects per app: but it is rarely worth it: one namespace keeps the
|
|
25
|
+
# services on a single private network, which is usually what they expect.
|
|
26
|
+
# ============================================================================
|
|
27
|
+
|
|
28
|
+
services:
|
|
29
|
+
web:
|
|
30
|
+
networks: [default, portta]
|
|
31
|
+
labels:
|
|
32
|
+
# Optional, and only useful because inference cannot get them right here:
|
|
33
|
+
# `portta.git.root` says where the repository actually starts when
|
|
34
|
+
# the Compose file is not at its root, and `portta.repo` gives the
|
|
35
|
+
# panel a repository link with no host-side Git at all. Delete both and
|
|
36
|
+
# everything behaves exactly as it does now.
|
|
37
|
+
# - "portta.repo=owner/monorepo"
|
|
38
|
+
# - "portta.git.root=/srv/dev/monorepo"
|
|
39
|
+
- "traefik.enable=true"
|
|
40
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
41
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-web.loadbalancer.server.port=3000"
|
|
42
|
+
|
|
43
|
+
admin:
|
|
44
|
+
networks: [default, portta]
|
|
45
|
+
labels:
|
|
46
|
+
- "traefik.enable=true"
|
|
47
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
48
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-admin.loadbalancer.server.port=3000"
|
|
49
|
+
|
|
50
|
+
api:
|
|
51
|
+
networks: [default, portta]
|
|
52
|
+
labels:
|
|
53
|
+
- "traefik.enable=true"
|
|
54
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
55
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-api.loadbalancer.server.port=8000"
|
|
56
|
+
|
|
57
|
+
# worker and importer are absent on purpose: they serve no HTTP, so they need
|
|
58
|
+
# no networks and no labels. They keep running exactly as they did.
|
|
59
|
+
|
|
60
|
+
networks:
|
|
61
|
+
portta:
|
|
62
|
+
external: true
|
|
63
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# A second copy of a project, running in parallel
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Copy to `.env` in the worktree. This is the entire mechanism: Compose derives
|
|
5
|
+
# container, network and volume names from COMPOSE_PROJECT_NAME, and the gateway
|
|
6
|
+
# derives hostnames from it.
|
|
7
|
+
#
|
|
8
|
+
# git worktree add ../base-empresarial-issue59 issue59
|
|
9
|
+
# cd ../base-empresarial-issue59
|
|
10
|
+
# cp <this file> .env
|
|
11
|
+
# docker compose -f compose.yaml -f compose.portta.yaml up -d
|
|
12
|
+
#
|
|
13
|
+
# -> http://base-empresarial-issue59-web.localhost
|
|
14
|
+
#
|
|
15
|
+
# The original environment keeps running. Nothing inside the project changed.
|
|
16
|
+
# Derive the name instead of inventing one:
|
|
17
|
+
#
|
|
18
|
+
# portta env namespace
|
|
19
|
+
# ============================================================================
|
|
20
|
+
|
|
21
|
+
COMPOSE_PROJECT_NAME=base-empresarial-issue59
|
|
22
|
+
|
|
23
|
+
# Saves repeating -f on every command.
|
|
24
|
+
COMPOSE_FILE=compose.yaml:compose.portta.yaml
|
|
25
|
+
|
|
26
|
+
# Must match PORTTA_NETWORK in the gateway's own .env.
|
|
27
|
+
PORTTA_NETWORK=portta
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Optional: say which project this worktree belongs to
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# COMPOSE_PROJECT_NAME is a per-worktree namespace, so five worktrees look like
|
|
33
|
+
# five projects in the panel. A `portta.project` label on any one service
|
|
34
|
+
# groups them back under one heading. Nothing depends on it: without it the
|
|
35
|
+
# panel still infers the worktree from the directory name, exactly as before.
|
|
36
|
+
#
|
|
37
|
+
# labels:
|
|
38
|
+
# - "portta.project=base-empresarial"
|
|
39
|
+
# - "portta.repo=owner/base-empresarial"
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# What must NOT be shared with the other worktree
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
# Do not point this environment at an external volume or a database the other
|
|
45
|
+
# copy also uses. Two worktrees writing to one database is the failure this
|
|
46
|
+
# whole mechanism exists to prevent, and it is silent until it corrupts
|
|
47
|
+
# something. Let Compose create per-namespace volumes.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# An HTTP service on an unusual internal port
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Traefik guesses the backend port from the image's EXPOSE when only one port
|
|
5
|
+
# is exposed. That guess is wrong whenever the image exposes one port and the
|
|
6
|
+
# application listens on another: which is most of the time once a command or
|
|
7
|
+
# an environment variable moves it.
|
|
8
|
+
#
|
|
9
|
+
# The symptom is a 502 from the gateway: the route matched, the backend did not
|
|
10
|
+
# answer. Stating the port removes the guess.
|
|
11
|
+
#
|
|
12
|
+
# Anything is fine here: 4321, 9090, 1313. The port is internal, so it never
|
|
13
|
+
# competes with another project.
|
|
14
|
+
# ============================================================================
|
|
15
|
+
|
|
16
|
+
services:
|
|
17
|
+
docs:
|
|
18
|
+
# e.g. a static site generator listening on 1313
|
|
19
|
+
networks: [default, portta]
|
|
20
|
+
labels:
|
|
21
|
+
- "traefik.enable=true"
|
|
22
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
23
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-docs.loadbalancer.server.port=1313"
|
|
24
|
+
|
|
25
|
+
# A service whose container serves HTTPS internally needs the scheme too,
|
|
26
|
+
# or Traefik will speak plaintext to a TLS port.
|
|
27
|
+
secure-backend:
|
|
28
|
+
networks: [default, portta]
|
|
29
|
+
labels:
|
|
30
|
+
- "traefik.enable=true"
|
|
31
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
32
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-secure-backend.loadbalancer.server.port=8443"
|
|
33
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-secure-backend.loadbalancer.server.scheme=https"
|
|
34
|
+
# Development backends usually have a self-signed certificate.
|
|
35
|
+
- "traefik.http.serverstransports.${COMPOSE_PROJECT_NAME}-insecure.insecureskipverify=true"
|
|
36
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-secure-backend.loadbalancer.serverstransport=${COMPOSE_PROJECT_NAME}-insecure@docker"
|
|
37
|
+
|
|
38
|
+
networks:
|
|
39
|
+
portta:
|
|
40
|
+
external: true
|
|
41
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Template 09: reach this project's database by hostname
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Add this on top of your project's own compose file, alongside whichever HTTP
|
|
5
|
+
# overlay you already use. It opts a datastore into hostname routing, so it is
|
|
6
|
+
# reachable on the gateway's shared port without publishing one of its own:
|
|
7
|
+
#
|
|
8
|
+
# psql "postgresql://user@<project>-postgres.<domain>:5432/db?sslmode=require"
|
|
9
|
+
#
|
|
10
|
+
# Requires PORTTA_TCP=true on the gateway. Read docs/product/guides/tcp-routing.md first:
|
|
11
|
+
# PostgreSQL and Redis work, MySQL cannot, and TLS is not optional.
|
|
12
|
+
#
|
|
13
|
+
# docker compose -f compose.yaml -f compose.portta.yaml \
|
|
14
|
+
# -f compose.portta-tcp.yaml up -d
|
|
15
|
+
#
|
|
16
|
+
# Three details worth copying verbatim:
|
|
17
|
+
#
|
|
18
|
+
# 1. The datastore joins `portta-access`, NOT the shared `portta`
|
|
19
|
+
# network. Databases have never been on the HTTP network and still are not.
|
|
20
|
+
#
|
|
21
|
+
# 2. The rule is HostSNIRegexp, not HostSNI, so this file does not have to know
|
|
22
|
+
# the gateway's domain. The same overlay then works unchanged against
|
|
23
|
+
# `.localhost` on a workstation and `.vpn.example.com` on a VPS. It grants
|
|
24
|
+
# nothing extra: whoever knows the project name can reach the entrypoint
|
|
25
|
+
# with the exact hostname anyway, and authentication is still PostgreSQL's.
|
|
26
|
+
#
|
|
27
|
+
# 3. Router and service names carry the project name, because they share one
|
|
28
|
+
# namespace across every project on the host. `portta doctor` reports
|
|
29
|
+
# a collision if two projects ever pick the same one.
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
services:
|
|
33
|
+
postgres:
|
|
34
|
+
networks:
|
|
35
|
+
- default # keep talking to the rest of the project privately
|
|
36
|
+
- portta-access
|
|
37
|
+
labels:
|
|
38
|
+
- "traefik.enable=true"
|
|
39
|
+
- "traefik.docker.network=${PORTTA_ACCESS_NETWORK:-portta-access}"
|
|
40
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-postgres.entrypoints=postgres"
|
|
41
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-postgres.rule=HostSNIRegexp(`^${COMPOSE_PROJECT_NAME}-postgres\\..+$`)"
|
|
42
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-postgres.tls=true"
|
|
43
|
+
# libpq 17+ offers ALPN `postgresql`; without this option the TLS
|
|
44
|
+
# handshake is rejected before any Postgres traffic happens.
|
|
45
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-postgres.tls.options=postgres@file"
|
|
46
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-postgres.service=${COMPOSE_PROJECT_NAME}-postgres"
|
|
47
|
+
- "traefik.tcp.services.${COMPOSE_PROJECT_NAME}-postgres.loadbalancer.server.port=5432"
|
|
48
|
+
|
|
49
|
+
redis:
|
|
50
|
+
networks:
|
|
51
|
+
- default
|
|
52
|
+
- portta-access
|
|
53
|
+
labels:
|
|
54
|
+
- "traefik.enable=true"
|
|
55
|
+
- "traefik.docker.network=${PORTTA_ACCESS_NETWORK:-portta-access}"
|
|
56
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-redis.entrypoints=redis"
|
|
57
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-redis.rule=HostSNIRegexp(`^${COMPOSE_PROJECT_NAME}-redis\\..+$`)"
|
|
58
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-redis.tls=true"
|
|
59
|
+
- "traefik.tcp.routers.${COMPOSE_PROJECT_NAME}-redis.service=${COMPOSE_PROJECT_NAME}-redis"
|
|
60
|
+
- "traefik.tcp.services.${COMPOSE_PROJECT_NAME}-redis.loadbalancer.server.port=6379"
|
|
61
|
+
|
|
62
|
+
# MySQL and MariaDB are deliberately absent. The MySQL protocol has the
|
|
63
|
+
# server speak first, so there is no SNI for the gateway to route on before
|
|
64
|
+
# it must choose a backend. Reach them with `portta access open`, which
|
|
65
|
+
# works for every protocol. docs/product/guides/tcp-routing.md explains why in full.
|
|
66
|
+
|
|
67
|
+
networks:
|
|
68
|
+
portta-access:
|
|
69
|
+
external: true
|
|
70
|
+
name: ${PORTTA_ACCESS_NETWORK:-portta-access}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# Mailpit: capture outbound mail in development
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# The UI (8025) is HTTP and joins the gateway. SMTP (1025) stays on the
|
|
5
|
+
# project's private network and is reached with `portta access open`.
|
|
6
|
+
#
|
|
7
|
+
# The base Compose file should declare the mailpit service itself; this overlay
|
|
8
|
+
# only adds the network and labels.
|
|
9
|
+
#
|
|
10
|
+
# -> http://<COMPOSE_PROJECT_NAME>-mailpit.localhost
|
|
11
|
+
# ============================================================================
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
mailpit:
|
|
15
|
+
networks: [default, portta]
|
|
16
|
+
labels:
|
|
17
|
+
- "traefik.enable=true"
|
|
18
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
19
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-mailpit.loadbalancer.server.port=8025"
|
|
20
|
+
|
|
21
|
+
networks:
|
|
22
|
+
portta:
|
|
23
|
+
external: true
|
|
24
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# RustFS: S3-compatible object storage in development
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# The console (9001) is HTTP and joins the gateway. The S3 API (9000) stays on
|
|
5
|
+
# the project's private network so sibling containers can talk to it as
|
|
6
|
+
# `rustfs:9000` without publishing a host port.
|
|
7
|
+
#
|
|
8
|
+
# The base Compose file should declare the rustfs service itself; this overlay
|
|
9
|
+
# only adds the network and labels for the console.
|
|
10
|
+
#
|
|
11
|
+
# -> http://<COMPOSE_PROJECT_NAME>-rustfs.localhost
|
|
12
|
+
# ============================================================================
|
|
13
|
+
|
|
14
|
+
services:
|
|
15
|
+
rustfs:
|
|
16
|
+
networks: [default, portta]
|
|
17
|
+
labels:
|
|
18
|
+
- "traefik.enable=true"
|
|
19
|
+
- "traefik.docker.network=${PORTTA_NETWORK:-portta}"
|
|
20
|
+
- "traefik.http.services.${COMPOSE_PROJECT_NAME}-rustfs.loadbalancer.server.port=9001"
|
|
21
|
+
|
|
22
|
+
networks:
|
|
23
|
+
portta:
|
|
24
|
+
external: true
|
|
25
|
+
name: ${PORTTA_NETWORK:-portta}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Running this project on Portta
|
|
2
|
+
|
|
3
|
+
> Copy this file into the project and adjust the names. It covers only what
|
|
4
|
+
> someone working on *this* project needs. The rules themselves live in the Dev
|
|
5
|
+
> Gateway repository, which stays the single source of truth.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
Portta must be running on this machine:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
portta status # if this fails: portta bootstrap && portta up local
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Starting
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
docker compose -f compose.yaml -f compose.portta.yaml up -d
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Set `COMPOSE_FILE=compose.yaml:compose.portta.yaml` in `.env` to drop the
|
|
22
|
+
`-f` flags.
|
|
23
|
+
|
|
24
|
+
## Namespace
|
|
25
|
+
|
|
26
|
+
`COMPOSE_PROJECT_NAME` is what keeps this environment separate from every other
|
|
27
|
+
one on the host: containers, network, volumes and hostnames all derive from it.
|
|
28
|
+
|
|
29
|
+
```env
|
|
30
|
+
# .env
|
|
31
|
+
COMPOSE_PROJECT_NAME=<project>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## URLs
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
portta urls --project <project>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Service | URL |
|
|
41
|
+
|---|---|
|
|
42
|
+
| web | `http://<project>-web.localhost` |
|
|
43
|
+
| api | `http://<project>-api.localhost` |
|
|
44
|
+
|
|
45
|
+
No port numbers, and no host port is published for either.
|
|
46
|
+
|
|
47
|
+
## Database and cache
|
|
48
|
+
|
|
49
|
+
Not published on the host. Two ways in:
|
|
50
|
+
|
|
51
|
+
**From inside the project**, with nothing to open and nothing to close:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
docker compose exec postgres psql -U <user> -d <db>
|
|
55
|
+
docker compose exec redis redis-cli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**From a GUI on the host**, through a temporary loopback bridge on a free port:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
portta access open --project <project> --service postgres
|
|
62
|
+
# -> 127.0.0.1:55431
|
|
63
|
+
|
|
64
|
+
portta access list
|
|
65
|
+
portta access close --project <project>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Point TablePlus, DBeaver or DataGrip at `127.0.0.1` and the port it printed.
|
|
69
|
+
Credentials are this project's, from its own `.env`.
|
|
70
|
+
|
|
71
|
+
## Working on two branches at once
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git worktree add ../<project>-issue59 issue59
|
|
75
|
+
cd ../<project>-issue59
|
|
76
|
+
echo "COMPOSE_PROJECT_NAME=<project>-issue59" >> .env
|
|
77
|
+
docker compose -f compose.yaml -f compose.portta.yaml up -d
|
|
78
|
+
# -> http://<project>-issue59-web.localhost
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Both run at once, each with its own database. The first environment is not
|
|
82
|
+
affected.
|
|
83
|
+
|
|
84
|
+
## Troubleshooting
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
portta doctor
|
|
88
|
+
portta urls --project <project>
|
|
89
|
+
docker compose logs -f
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**404**: the service did not opt in, or Traefik has not discovered it yet.
|
|
93
|
+
**502**: the route matched but the backend did not answer, usually the wrong
|
|
94
|
+
port in the overlay, or the service is not on the `portta` network.
|
|
95
|
+
|
|
96
|
+
More: Portta's `docs/product/guides/troubleshooting.md`.
|
|
97
|
+
|
|
98
|
+
## Without the gateway
|
|
99
|
+
|
|
100
|
+
The overlay only adds networks and labels, so the project still runs on its own:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
docker compose up -d
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You lose hostname routing, so you will need published ports. Put those in your
|
|
107
|
+
personal `compose.override.yaml`, not in the shared files.
|