244 lines
6.7 KiB
Markdown
244 lines
6.7 KiB
Markdown
# Deploy Xread
|
|
|
|
This guide deploys the standalone Xread stack behind Caddy with a Compose-compatible container engine.
|
|
|
|
It assumes you want the common public layout:
|
|
|
|
- `r.your-domain.example` -> `crawl`
|
|
- `s.your-domain.example/search` -> `search`
|
|
- `s.your-domain.example/?q=...` -> `serp`
|
|
|
|
## What Gets Deployed
|
|
|
|
The stack uses four containers:
|
|
|
|
- `crawl`: page-to-Markdown service
|
|
- `search`: text-oriented web search
|
|
- `serp`: structured JSON search
|
|
- `caddy`: TLS termination and reverse proxy
|
|
|
|
Internally, Caddy talks to the HTTP/1 fallback port on each app container.
|
|
|
|
## Runtime Scope
|
|
|
|
This deployment layout uses the same image, Compose file, environment variables, and reverse-proxy topology across the common OCI runtimes:
|
|
|
|
- Docker Engine
|
|
- Docker Desktop
|
|
- Podman
|
|
- `nerdctl` with containerd
|
|
- Docker-compatible desktop wrappers such as OrbStack, Colima, and Rancher Desktop
|
|
|
|
If you need engine-specific command names, see [`container-engines.md`](./container-engines.md).
|
|
|
|
## Prerequisites
|
|
|
|
- A Linux server with an OCI-compatible container engine and a Compose-compatible CLI
|
|
- DNS records for your subdomains pointing directly at the server
|
|
- Ports `80` and `443` reachable from the public internet
|
|
|
|
> [!IMPORTANT]
|
|
> Automatic TLS will not work until your public DNS records really resolve to the server's public IP. Verify that first with a public resolver, not only your local machine cache.
|
|
|
|
## Files
|
|
|
|
Use the templates included in this repository:
|
|
|
|
- [`deploy/docker-compose.yml`](../deploy/docker-compose.yml)
|
|
- [`deploy/Caddyfile`](../deploy/Caddyfile)
|
|
- [`deploy/.env.example`](../deploy/.env.example)
|
|
|
|
## 1. Prepare the runtime
|
|
|
|
Install one supported runtime on the host, then verify that both the engine CLI and the Compose-compatible CLI are available. The deployment steps after this point stay the same.
|
|
|
|
### Docker
|
|
|
|
```bash
|
|
docker version
|
|
docker compose version
|
|
```
|
|
|
|
### Podman
|
|
|
|
```bash
|
|
podman version
|
|
podman compose version
|
|
```
|
|
|
|
### nerdctl
|
|
|
|
```bash
|
|
nerdctl version
|
|
nerdctl compose version
|
|
```
|
|
|
|
Optional but recommended on very small hosts:
|
|
|
|
```bash
|
|
fallocate -l 1G /swapfile
|
|
chmod 600 /swapfile
|
|
mkswap /swapfile
|
|
swapon /swapfile
|
|
grep -q '^/swapfile ' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
|
```
|
|
|
|
## 2. Copy the deployment assets
|
|
|
|
```bash
|
|
mkdir -p /opt/xread
|
|
cd /opt/xread
|
|
```
|
|
|
|
Copy these files into `/opt/xread`:
|
|
|
|
- `docker-compose.yml`
|
|
- `Caddyfile`
|
|
- `.env`
|
|
|
|
Start from the repository template:
|
|
|
|
```bash
|
|
cp deploy/.env.example /opt/xread/.env
|
|
```
|
|
|
|
Choose the Compose-compatible command you plan to use:
|
|
|
|
```bash
|
|
# Docker / Docker Desktop / OrbStack / Colima
|
|
export COMPOSE_CMD="docker compose"
|
|
|
|
# Podman
|
|
export COMPOSE_CMD="podman compose"
|
|
|
|
# nerdctl
|
|
export COMPOSE_CMD="nerdctl compose"
|
|
```
|
|
|
|
## 3. Configure environment variables
|
|
|
|
Edit `/opt/xread/.env`:
|
|
|
|
```dotenv
|
|
R_HOST=r.your-domain.example
|
|
S_HOST=s.your-domain.example
|
|
ACME_EMAIL=ops@your-domain.example
|
|
XREAD_IMAGE=ghcr.io/xixu-me/xread:latest
|
|
PUBLIC_HTTP_PORT=80
|
|
PUBLIC_HTTPS_PORT=443
|
|
XREAD_LOOPBACK_HOST=127.0.0.1
|
|
XREAD_CRAWL_LOOPBACK_PORT=3001
|
|
XREAD_SEARCH_LOOPBACK_PORT=3101
|
|
XREAD_SERP_LOOPBACK_PORT=3201
|
|
SERPER_SEARCH_API_KEY=
|
|
BRAVE_SEARCH_API_KEY=
|
|
LOCAL_PROXY_URLS=
|
|
```
|
|
|
|
Notes:
|
|
|
|
- `PUBLIC_HTTP_PORT` and `PUBLIC_HTTPS_PORT` default to `80` and `443`
|
|
- If your host or policy does not allow binding `80` and `443`, change those to higher ports such as `8080` and `8443`
|
|
- `XREAD_LOOPBACK_HOST` and the three backend loopback ports control the local operator-only smoke-test endpoints
|
|
- `SERPER_SEARCH_API_KEY` is optional but recommended for more stable Google/Bing search results
|
|
- `BRAVE_SEARCH_API_KEY` is optional
|
|
- `LOCAL_PROXY_URLS` is optional and can stay empty
|
|
- Other runtime variables exist, but this template keeps the deployment file focused on the common deployment subset
|
|
|
|
## 4. Start the stack
|
|
|
|
```bash
|
|
cd /opt/xread
|
|
$COMPOSE_CMD pull
|
|
$COMPOSE_CMD up -d
|
|
```
|
|
|
|
Check status:
|
|
|
|
```bash
|
|
$COMPOSE_CMD ps
|
|
$COMPOSE_CMD logs --tail=100
|
|
```
|
|
|
|
On small VPS instances, give the stack a little time on first boot. Chrome-based workers can take tens of seconds to warm up.
|
|
|
|
## 5. Verify the deployment
|
|
|
|
There are three useful checkpoints:
|
|
|
|
- direct loopback checks against the three backend containers
|
|
- public-domain checks through Caddy
|
|
- temporary `--resolve` checks before public DNS is live
|
|
|
|
### Local container checks
|
|
|
|
These examples use the default loopback values from `deploy/.env.example`:
|
|
|
|
```bash
|
|
curl http://127.0.0.1:3001/http://example.com
|
|
curl "http://127.0.0.1:3101/search?q=example%20domain&num=3&provider=google"
|
|
curl "http://127.0.0.1:3201/?q=example%20domain&num=3&provider=google"
|
|
```
|
|
|
|
Those loopback ports are published only on `XREAD_LOOPBACK_HOST` for operator smoke tests. They are not exposed publicly unless you change that host binding yourself.
|
|
|
|
### Public domain checks
|
|
|
|
These examples assume the default public ports `80` and `443`:
|
|
|
|
```bash
|
|
curl https://r.your-domain.example/http://example.com
|
|
curl "https://s.your-domain.example/search?q=example%20domain&num=3&provider=google"
|
|
curl "https://s.your-domain.example/?q=example%20domain&num=3&provider=google"
|
|
```
|
|
|
|
If DNS is not live yet, you can still smoke-test the reverse proxy path from a client machine:
|
|
|
|
```bash
|
|
curl --resolve r.your-domain.example:80:YOUR_SERVER_IP -H "Host: r.your-domain.example" http://r.your-domain.example/http://example.com
|
|
curl --resolve s.your-domain.example:80:YOUR_SERVER_IP -H "Host: s.your-domain.example" "http://s.your-domain.example/search?q=example%20domain&num=3&provider=google"
|
|
```
|
|
|
|
## Operational Notes
|
|
|
|
- The app containers store data under named volumes for `storage` and `db`
|
|
- Caddy stores certificates and config in named volumes
|
|
- The image already contains Chrome and common font packages
|
|
- Public traffic terminates at Caddy; app containers stay on the internal Compose network
|
|
|
|
## Upgrade
|
|
|
|
```bash
|
|
cd /opt/xread
|
|
$COMPOSE_CMD pull
|
|
$COMPOSE_CMD up -d
|
|
```
|
|
|
|
For safer upgrades, pin `XREAD_IMAGE` to a digest instead of `latest`.
|
|
|
|
## Troubleshooting
|
|
|
|
### TLS does not issue
|
|
|
|
Usually means one of:
|
|
|
|
- DNS is not pointing at the server yet
|
|
- Port `80` or `443` is blocked
|
|
- Another process is already listening on those ports
|
|
|
|
### The host cannot bind 80 or 443
|
|
|
|
Use the same deployment knobs regardless of engine:
|
|
|
|
- set `PUBLIC_HTTP_PORT` and `PUBLIC_HTTPS_PORT` to higher ports such as `8080` and `8443`
|
|
- terminate TLS on an external load balancer if you want to keep public `443`
|
|
- forward `80` and `443` from another service if your host standard requires it
|
|
|
|
### Search works poorly without an API key
|
|
|
|
That is expected. The standalone fallback path is useful for bootstrapping and low-volume use, but provider-backed search is more stable.
|
|
|
|
### Browser rendering fails on small hosts
|
|
|
|
Make sure swap is enabled and the host is not memory-starved. The image includes Chrome, so low-memory VPS instances can benefit from at least `1G` swap.
|