Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ name: cd
# <dockerhub-username>/paca-api:<tag> — Go API service Docker image
# ghcr.io/paca-ai/paca-realtime:<tag> — Node realtime service Docker image
# <dockerhub-username>/paca-realtime:<tag> — Node realtime service Docker image
# ghcr.io/paca-ai/paca-web:<tag> — Web app Docker image (nginx)
# <dockerhub-username>/paca-web:<tag> — Web app Docker image (nginx)
# ghcr.io/paca-ai/paca-web:<tag> — Web app Docker image (Caddy)
# <dockerhub-username>/paca-web:<tag> — Web app Docker image (Caddy)
# ghcr.io/paca-ai/paca-ai-agent:<tag> — AI Agent service Docker image
# <dockerhub-username>/paca-ai-agent:<tag> — AI Agent service Docker image
# registry.npmjs.org @paca-ai/paca-mcp — MCP server npm package
# registry.npmjs.org @paca-ai/plugin-sdk-react — Plugin frontend SDK npm package
# GitHub Release assets:
# install.sh — one-shot interactive install script
# upgrade.sh — upgrades an existing installation in place
# docker-compose.yml — standalone compose (no source tree required)
# gateway.conf — nginx gateway configuration
# Caddyfile — Caddy gateway configuration

on:
release:
Expand Down Expand Up @@ -138,9 +139,9 @@ jobs:
# ─────────────────────────────────────────────────────────────────────────────
# Web app image
# ─────────────────────────────────────────────────────────────────────────────
# The pre-built image uses relative URLs that work with nginx /api proxy
# out of the box, since the API client now derives the base URL from
# window.location.origin at runtime.
# The pre-built image uses relative URLs that work with the Caddy /api
# proxy out of the box, since the API client now derives the base URL
# from window.location.origin at runtime.
web-image:
name: Build and push Web image
runs-on: ubuntu-latest
Expand Down Expand Up @@ -313,10 +314,11 @@ jobs:
# ─────────────────────────────────────────────────────────────────────────────
# Deployment assets → GitHub Release
#
# Uploads three files so that users can run Paca without cloning the repo:
# Uploads four files so that users can run or upgrade Paca without cloning the repo:
# install.sh — interactive setup wizard (download + configure + start)
# upgrade.sh — upgrades an existing installation in place
# docker-compose.yml — standalone compose referencing pre-built DockerHub images
# gateway.conf — nginx gateway configuration required by the compose file
# Caddyfile — Caddy gateway configuration required by the compose file
#
# End-users download and run:
# curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/install.sh -o install.sh
Expand All @@ -337,16 +339,18 @@ jobs:
run: |
# Rename files to the names end-users will download.
cp deploy/docker-compose.prod.yml docker-compose.yml
cp deploy/nginx/gateway.conf gateway.conf
cp deploy/caddy/Caddyfile Caddyfile
cp scripts/install.sh install.sh
chmod +x install.sh
cp scripts/upgrade.sh upgrade.sh
chmod +x install.sh upgrade.sh

- name: Upload assets to GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ github.event.release.tag_name }}" \
install.sh \
upgrade.sh \
docker-compose.yml \
gateway.conf \
Caddyfile \
--clobber
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ The script walks you through configuration interactively and starts the full sta
```bash
mkdir paca && cd paca
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/docker-compose.yml -o docker-compose.yml
mkdir -p nginx
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/gateway.conf -o nginx/gateway.conf
mkdir -p caddy
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/Caddyfile -o caddy/Caddyfile
```

**2. Download the environment template**
Expand Down Expand Up @@ -250,14 +250,16 @@ docker compose --env-file .env up -d

### Upgrading to a new version

From the directory where your `docker-compose.yml` lives:
From the directory where your `docker-compose.yml` and `.env` live, run the upgrade
script published with each release — it refreshes `docker-compose.yml` and the
Caddyfile (with backups) and restarts the stack:

```bash
docker compose pull
docker compose --env-file .env up -d
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/upgrade.sh -o upgrade.sh
bash upgrade.sh
```

Database migrations run automatically on API startup.
Database migrations run automatically on API startup. See [deploy/README.md](deploy/README.md#upgrading-to-a-new-version) for pinning a specific version or passing through `--scale` flags.

---

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ _Goal: a working, self-hostable core that a small team can actually use._
- ✅ Docker Compose single-command setup
- ✅ Interactive install script for Linux servers
- ✅ PostgreSQL + Valkey bundled by default
- ✅ Nginx gateway with service routing
- ✅ Caddy gateway with service routing
- ✅ Environment-based configuration (`.env`)

### Core Platform
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Security reports may cover:
- Unsafe AI agent actions or privilege escalation.
- WASM plugin sandbox escapes or capability bypasses.
- Supply chain or dependency risks.
- Deployment misconfiguration risks (Docker Compose, nginx, environment variables).
- Deployment misconfiguration risks (Docker Compose, Caddy, environment variables).
- API injection risks (SQL injection, command injection, XSS).

## Supported Versions
Expand Down
2 changes: 1 addition & 1 deletion apps/mcp/src/plugin-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export async function loadPlugins(config: PacaConfig): Promise<PluginRegistry> {
for (const plugin of mcpPlugins) {
// biome-ignore lint/style/noNonNullAssertion: filtered above
const url = plugin.manifest.mcp!.remoteEntryUrl;
// Use gatewayURL when set — MCP bundles are served by the gateway (nginx),
// Use gatewayURL when set — MCP bundles are served by the gateway (Caddy),
// not the API service, so relative URLs must be resolved against the gateway.
const pluginBaseURL = config.gatewayURL ?? config.baseURL;
try {
Expand Down
2 changes: 1 addition & 1 deletion apps/mcp/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface PacaConfig {
* Base URL used to resolve plugin MCP entry URLs (e.g. relative paths like
* `/plugins-mcp/<id>/mcp.js`). Defaults to `baseURL` when not set.
*
* In Docker deployments the MCP bundles are served by the gateway (nginx),
* In Docker deployments the MCP bundles are served by the gateway (Caddy),
* not by the API service, so this should be set to the gateway's internal
* URL (e.g. `http://gateway`).
*/
Expand Down
12 changes: 12 additions & 0 deletions apps/web/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:3000 {
root * /srv
header -Server

# Vite emits hashed filenames under /assets — cache aggressively
@assets path /assets/*
header @assets Cache-Control "public, max-age=31536000, immutable"

# All other routes fall back to index.html for client-side routing
try_files {path} /index.html
file_server
}
10 changes: 5 additions & 5 deletions apps/web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ COPY . .
RUN bun run build

# ── Runtime stage ─────────────────────────────────────────────────────────────
FROM nginx:1.29-alpine
FROM caddy:2-alpine

# Replace the default nginx config with our SPA config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Replace the default Caddy config with our SPA config
COPY Caddyfile /etc/caddy/Caddyfile

# Copy built assets from the build stage
COPY --from=builder /build/dist /usr/share/nginx/html
COPY --from=builder /build/dist /srv

EXPOSE 3000

CMD ["nginx", "-g", "daemon off;"]
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
22 changes: 0 additions & 22 deletions apps/web/nginx.conf

This file was deleted.

2 changes: 1 addition & 1 deletion deploy/.env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ ADMIN_PASSWORD=replace-with-a-strong-admin-password
# MinIO is started as a sidecar container using the credentials below.
STORAGE_PROVIDER=minio
STORAGE_ENDPOINT=minio:9000
# Public URL through which browsers reach the object store via the nginx gateway.
# Public URL through which browsers reach the object store via the Caddy gateway.
# Update to match your domain when deploying behind a real hostname.
STORAGE_PUBLIC_URL=http://localhost/storage
STORAGE_REGION=us-east-1
Expand Down
67 changes: 60 additions & 7 deletions deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ Service container definitions live with each service:
### Recommended: install script

The easiest way to run Paca without cloning the repository is via the install script
published with each release. It downloads the compose file and nginx config, walks you
through configuration interactively (database, storage, AI agent), generates a `.env`
with strong random secrets, and starts the stack.
published with each release. It downloads the compose file and Caddyfile, walks you
through configuration interactively (database, storage, networking/HTTPS, AI agent),
generates a `.env` with strong random secrets, and starts the stack.

```bash
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/install.sh -o install.sh
Expand All @@ -41,16 +41,17 @@ The installer supports:
| External PostgreSQL | Supply a `DATABASE_URL`; postgres container is suppressed |
| Self-hosted MinIO | Starts a MinIO container for S3-compatible file storage (default) |
| AWS S3 | Supply AWS credentials; MinIO container is suppressed |
| HTTPS | Enabled by default — Let's Encrypt for a real domain, Caddy's local CA otherwise; can be disabled for plain HTTP |
| AI Agent | Enabled by default; can be skipped to reduce resource usage |

### Manual setup

Download the two required files from the latest release:

```bash
mkdir -p paca/nginx && cd paca
mkdir -p paca/caddy && cd paca
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/docker-compose.yml -o docker-compose.yml
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/gateway.conf -o nginx/gateway.conf
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/Caddyfile -o caddy/Caddyfile
```

Download the example environment file and edit it:
Expand Down Expand Up @@ -82,6 +83,34 @@ Start the full stack (bundled PostgreSQL + MinIO):
docker compose --env-file .env up -d
```

**With HTTPS** — set `SITE_ADDRESS` to any concrete domain or IP address and Caddy
handles certificates automatically, choosing the right kind for what you give it:

```bash
# In .env: set SITE_ADDRESS to your domain/IP, and PUBLIC_URL/COOKIE_SECURE to match.
SITE_ADDRESS=paca.example.com
PUBLIC_URL=https://paca.example.com
COOKIE_SECURE=true
```

```bash
docker compose --env-file .env up -d
```

- A real domain name with DNS already pointed here gets a trusted Let's Encrypt
certificate, renewed automatically. Ports 80 and 443 must both be reachable from the
internet for the ACME challenge to succeed.
- An IP address, `localhost`, `*.localhost`, or anything else that isn't a publicly
resolvable domain gets a certificate from Caddy's own local certificate authority
instead — traffic is still encrypted, but browsers will show a trust warning since
that CA isn't publicly trusted.

Either way, certificates persist in the `caddy_data` volume across restarts.

Without `SITE_ADDRESS` (or set to a bare port like `:80`), the gateway serves plain
HTTP — the simplest option, and the right one when another proxy or load balancer in
front of this server already terminates TLS.

**With external PostgreSQL** (suppress the bundled container):

```bash
Expand Down Expand Up @@ -110,7 +139,31 @@ docker compose --env-file .env up -d --scale postgres=0 --scale minio=0

### Upgrading to a new version

Pull the latest images and restart the stack:
**Recommended: upgrade script.** From the directory where your `docker-compose.yml` and
`.env` live, run the same upgrade script published with each release. It backs up
`docker-compose.yml`, `caddy/Caddyfile`, and `.env` before overwriting them, refreshes
the compose file and Caddyfile, re-pins image versions when you request a specific
release, then pulls and restarts the stack:

```bash
curl -fsSL https://github.com/Paca-AI/paca/releases/latest/download/upgrade.sh -o upgrade.sh
bash upgrade.sh
```

Pin to a specific release instead of `latest`:

```bash
PACA_VERSION=v1.2.3 bash upgrade.sh
```

Pass through any `--scale` flags you used originally:

```bash
bash upgrade.sh --scale web=0 --scale minio=0
```

**Manual:** pull the latest images and restart the stack — this is enough when
`docker-compose.yml` and the Caddyfile haven't changed shape since your last upgrade:

```bash
docker compose pull
Expand Down Expand Up @@ -192,7 +245,7 @@ and use Docker Compose only for PostgreSQL and Valkey.

| Service | Port | Notes |
|---|---|---|
| Gateway (nginx) | **3000** | Main entry point — `http://localhost:3000` |
| Gateway (Caddy) | **3000** | Main entry point — `http://localhost:3000` |
| PostgreSQL | 5432 | Local database for development |
| Valkey | 6379 | Local cache / event streams |
| API | 8080 (internal) | Routed via gateway at `/api/` |
Expand Down
Loading
Loading