@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.
Files changed (72) hide show
  1. package/dist/assets/skills/portta-workflows/SKILL.md +280 -0
  2. package/dist/assets/workflows/bake-off.workflow.js +171 -0
  3. package/dist/assets/workflows/code-review.workflow.js +179 -0
  4. package/dist/assets/workflows/deep-research.workflow.js +222 -0
  5. package/dist/assets/workflows/multi-provider-review.workflow.js +168 -0
  6. package/dist/assets/workflows/provider-debate.workflow.js +140 -0
  7. package/dist/assets/workflows/second-opinion.workflow.js +175 -0
  8. package/dist/cli.js +129793 -0
  9. package/dist/documentation.json +1 -0
  10. package/dist/host.js +73741 -0
  11. package/dist/runtime/.env.example +460 -0
  12. package/dist/runtime/VERSION +1 -0
  13. package/dist/runtime/config/traefik/dynamic/auth.example.yaml.disabled +24 -0
  14. package/dist/runtime/config/traefik/dynamic/middlewares.yaml +35 -0
  15. package/dist/runtime/config/traefik/dynamic/tcp.yaml +23 -0
  16. package/dist/runtime/docker/compose/attach/host.yaml +24 -0
  17. package/dist/runtime/docker/compose/attach/tailscale.yaml +70 -0
  18. package/dist/runtime/docker/compose/compose.yaml +214 -0
  19. package/dist/runtime/docker/compose/features/auth-build.yaml +19 -0
  20. package/dist/runtime/docker/compose/features/auth-dev.yaml +20 -0
  21. package/dist/runtime/docker/compose/features/cloudflare-tunnel.yaml +69 -0
  22. package/dist/runtime/docker/compose/features/dashboard-tailscale.yaml +21 -0
  23. package/dist/runtime/docker/compose/features/dashboard.yaml +21 -0
  24. package/dist/runtime/docker/compose/features/db.yaml +63 -0
  25. package/dist/runtime/docker/compose/features/panel-domain.yaml +42 -0
  26. package/dist/runtime/docker/compose/features/panel-public.yaml +47 -0
  27. package/dist/runtime/docker/compose/features/panel-webhook.yaml +46 -0
  28. package/dist/runtime/docker/compose/features/taskflow.yaml +38 -0
  29. package/dist/runtime/docker/compose/features/tcp-tailscale.yaml +30 -0
  30. package/dist/runtime/docker/compose/features/tcp.yaml +40 -0
  31. package/dist/runtime/docker/compose/features/web-bind.yaml +19 -0
  32. package/dist/runtime/docker/compose/features/web-build.yaml +21 -0
  33. package/dist/runtime/docker/compose/features/web-dev.yaml +54 -0
  34. package/dist/runtime/docker/compose/features/web-vpn.yaml +24 -0
  35. package/dist/runtime/docker/compose/features/web.yaml +283 -0
  36. package/dist/runtime/docker/compose/profiles/local-tls.yaml +23 -0
  37. package/dist/runtime/docker/compose/profiles/local.yaml +15 -0
  38. package/dist/runtime/docker/compose/profiles/public.yaml +19 -0
  39. package/dist/runtime/docker/compose/profiles/remote-tls-dns.yaml +26 -0
  40. package/dist/runtime/docker/compose/profiles/remote-tls-http.yaml +32 -0
  41. package/dist/runtime/docker/compose/profiles/remote-tls.yaml +31 -0
  42. package/dist/runtime/docker/compose/profiles/remote.yaml +23 -0
  43. package/dist/runtime/docker/images/README.md +12 -0
  44. package/dist/runtime/docker/images/apply/Dockerfile +45 -0
  45. package/dist/runtime/docker/images/sandbox/Dockerfile +79 -0
  46. package/dist/runtime/docker/images/sandbox/README.md +109 -0
  47. package/dist/runtime/docker/images/sandbox/VERSION +1 -0
  48. package/dist/runtime/docker/images/sandbox/agents/claude-code.sh +18 -0
  49. package/dist/runtime/docker/images/sandbox/agents/codex.sh +4 -0
  50. package/dist/runtime/docker/images/sandbox/agents/opencode.sh +4 -0
  51. package/dist/runtime/docker/images/sandbox/agents/pi.sh +4 -0
  52. package/dist/runtime/docker/images/sandbox/scripts/entrypoint.sh +8 -0
  53. package/dist/runtime/docker/images/sandbox/scripts/generate-manifest.mjs +81 -0
  54. package/dist/runtime/docker/images/sandbox/scripts/install-tools.sh +46 -0
  55. package/dist/runtime/docker/images/sandbox/scripts/verify-image.sh +18 -0
  56. package/dist/runtime/docker/images/toolbox/Dockerfile +38 -0
  57. package/dist/runtime/scripts/lib/runner-exec.sh +189 -0
  58. package/dist/runtime/templates/README.md +43 -0
  59. package/dist/runtime/templates/overlays/01-single-web.yaml +23 -0
  60. package/dist/runtime/templates/overlays/02-web-api.yaml +30 -0
  61. package/dist/runtime/templates/overlays/03-web-api-postgres.yaml +33 -0
  62. package/dist/runtime/templates/overlays/04-web-api-postgres-redis.yaml +30 -0
  63. package/dist/runtime/templates/overlays/05-multiple-apis.yaml +37 -0
  64. package/dist/runtime/templates/overlays/06-monorepo.yaml +63 -0
  65. package/dist/runtime/templates/overlays/07-worktree.env +47 -0
  66. package/dist/runtime/templates/overlays/08-nonstandard-port.yaml +41 -0
  67. package/dist/runtime/templates/overlays/09-tcp-routing.yaml +70 -0
  68. package/dist/runtime/templates/overlays/10-mailpit.yaml +24 -0
  69. package/dist/runtime/templates/overlays/11-rustfs.yaml +25 -0
  70. package/dist/runtime/templates/project/PORTTA.md +107 -0
  71. package/dist/supervisor.js +23928 -0
  72. 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.