Albatross 是自架的航班價格追蹤系統。本 image 是其中的 API server(Hono + TypeScript,Better Auth 登入、業務 API、內建排程器,唯一的 DB 寫入者)。
本 image 不能單獨執行,必須與 jhangyu/albatross-scraper, jhangyu/albatross-browser-runtime, jhangyu/albatross-web 及 PostgreSQL 一起以 docker compose 啟動。
原始碼:https://github.com/jhangyu/albatross
docker-compose.prod.yml。不需要任何 env 檔。docker compose -f docker-compose.prod.yml up -d
http://<主機>:43280,註冊帳號。第一位註冊者自動成為 admin,其餘設定(自動排程、SMTP 通知信、來源帳號等)都在網頁的管理頁完成。注意事項:
secrets 服務首次啟動時自動產生,存在 ${DATA_PATH}/secrets。DATA_PATH(預設 ./data-prod)。請備份 ${DATA_PATH}/secrets 與 ${DATA_PATH}/postgres。web 對外開 port;db、api、scraper、browser-runtime 只在 compose 內部網路連線。.env.prod 範例中要改的行存成 .env.prod,改用 docker compose --env-file .env.prod -f docker-compose.prod.yml up -d 啟動。docker compose 一律讀取目前目錄的 .env,部署目錄內不要放其他 .env。docker compose -f docker-compose.prod.yml pull,再執行上面的 up -d。# Runs with NO env file: `docker compose -f docker-compose.prod.yml up -d`.
# Every value has a default below. Optional overrides go in .env.prod (see .env.prod.example)
# and are passed with `--env-file .env.prod`.
# TRAP: `docker compose` always loads the project-root `.env` for variable interpolation, even
# with `-f`. Inside a checkout that has a dev root `.env`, pass `--env-file .env.prod` or run
# from another directory, or the stack inherits the dev WEB_PORT and DATA_PATH and collides.
services:
# One-shot: env value wins, else the persisted file is kept, else a 64-hex value is generated once (R-51).
# Layout ${DATA_PATH}/secrets/<name>/value: root 0700, dir 0755, file 0444 (browser-runtime runs non-root).
secrets:
container_name: albatross-prod-secrets
image: postgres:18-alpine
entrypoint: ["/bin/sh", "-ec"]
environment:
POSTGRES_USER: ${POSTGRES_USER:-albatross}
POSTGRES_DB: ${POSTGRES_DB:-albatross}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-}
BROWSER_RUNTIME_TOKEN: ${BROWSER_RUNTIME_TOKEN:-}
volumes:
- ${DATA_PATH:-./data-prod}/secrets:/secrets
restart: "no"
command:
- |
chmod 700 /secrets
put() { mkdir -p /secrets/$$1; chmod 755 /secrets/$$1; printf %s "$$2" > /secrets/$$1/value.tmp; chmod 444 /secrets/$$1/value.tmp; mv -f /secrets/$$1/value.tmp /secrets/$$1/value; }
get() { cat /secrets/$$1/value 2>/dev/null || true; }
resolve() {
cur=$$(get $$1)
if [ -n "$$2" ]; then
# postgres reads its password only at first init: a changed env value would split api and db.
if [ "$$1" = postgres_password ] && [ -n "$$cur" ] && [ "$$cur" != "$$2" ]; then
echo "POSTGRES_PASSWORD differs from DATA_PATH/secrets/postgres_password/value. Run ALTER ROLE first, then delete that file." >&2; exit 1
fi
[ "$$cur" = "$$2" ] || put $$1 "$$2"
elif [ -z "$$cur" ]; then put $$1 "$$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"; fi
}
resolve postgres_password "$$POSTGRES_PASSWORD"
resolve better_auth_secret "$$BETTER_AUTH_SECRET"
resolve browser_runtime_token "$$BROWSER_RUNTIME_TOKEN"
# ponytail: an env POSTGRES_PASSWORD with URL-special chars breaks this URL (same ceiling as before). Generated values are hex.
put database_url "postgresql://$$POSTGRES_USER:$$(get postgres_password)@db:5432/$$POSTGRES_DB"
db:
container_name: albatross-prod-db
image: postgres:18-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:-albatross}
POSTGRES_PASSWORD_FILE: /run/albatross/postgres_password/value
POSTGRES_DB: ${POSTGRES_DB:-albatross}
volumes:
- ${DATA_PATH:-./data-prod}/postgres:/var/lib/postgresql
- ${DATA_PATH:-./data-prod}/secrets/postgres_password:/run/albatross/postgres_password:ro
depends_on:
secrets:
condition: service_completed_successfully
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-albatross}"]
interval: 30s
start_interval: 1s
start_period: 10s
timeout: 2s
retries: 3
deploy:
resources:
limits:
memory: 512M
restart: unless-stopped
api:
container_name: albatross-prod-api
image: jhangyu/albatross-api:${IMAGE_TAG:-latest}
environment:
PORT: 4891
DATABASE_URL_FILE: /run/albatross/database_url/value
BETTER_AUTH_SECRET_FILE: /run/albatross/better_auth_secret/value
WORKER_URL: http://scraper:8000
BROWSER_RUNTIME_WS_URL: ws://browser-runtime:8000/vnc/ws
BROWSER_RUNTIME_TOKEN_FILE: /run/albatross/browser_runtime_token/value
# Seals the SMTP password stored by the admin settings page (base64 of 32 bytes). Optional:
# empty = the api generates a key on first use into the api volume below and keeps it.
SETTINGS_ENCRYPTION_KEY: ${SETTINGS_ENCRYPTION_KEY:-}
volumes:
# Auto-generated settings encryption key (settings.key), kept across container recreation.
- ${DATA_PATH:-./data-prod}/api:/app/data/api
- ${DATA_PATH:-./data-prod}/secrets/database_url:/run/albatross/database_url:ro
- ${DATA_PATH:-./data-prod}/secrets/better_auth_secret:/run/albatross/better_auth_secret:ro
- ${DATA_PATH:-./data-prod}/secrets/browser_runtime_token:/run/albatross/browser_runtime_token:ro
depends_on:
db:
condition: service_healthy
secrets:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4891/health"]
interval: 30s
start_interval: 2s
start_period: 15s
timeout: 3s
retries: 3
deploy:
resources:
limits:
memory: 512M
restart: unless-stopped
scraper:
container_name: albatross-prod-scraper
image: jhangyu/albatross-scraper:${IMAGE_TAG:-latest}
environment:
BROWSER_RUNTIME_URL: http://browser-runtime:8000
BROWSER_RUNTIME_TOKEN_FILE: /run/albatross/browser_runtime_token/value
volumes:
- ${DATA_PATH:-./data-prod}/profiles:/app/profiles
- ${DATA_PATH:-./data-prod}/logs:/app/data/logs
- ${DATA_PATH:-./data-prod}/secrets/browser_runtime_token:/run/albatross/browser_runtime_token:ro
depends_on:
browser-runtime:
condition: service_healthy
secrets:
condition: service_completed_successfully
deploy:
resources:
limits:
memory: 512M
restart: unless-stopped
browser-runtime:
container_name: albatross-prod-runtime
image: jhangyu/albatross-browser-runtime:${IMAGE_TAG:-latest}
environment:
BROWSER_RUNTIME_TOKENS_FILE: /run/albatross/browser_runtime_token/value
# Chromium interactive sessions and transient Firefox bootstraps need headroom.
shm_size: 2g
volumes:
- ${DATA_PATH:-./data-prod}/browser-runtime:/app/state
- ${DATA_PATH:-./data-prod}/secrets/browser_runtime_token:/run/albatross/browser_runtime_token:ro
depends_on:
secrets:
condition: service_completed_successfully
healthcheck:
test: ["CMD", "curl", "-sf", "http://127.0.0.1:8000/health"]
interval: 30s
start_interval: 2s
start_period: 15s
timeout: 3s
retries: 3
deploy:
resources:
limits:
memory: 3G
restart: unless-stopped
web:
container_name: albatross-prod-web
image: jhangyu/albatross-web:${IMAGE_TAG:-latest}
ports:
- "${WEB_PORT:-43280}:80"
depends_on:
api:
condition: service_healthy
deploy:
resources:
limits:
memory: 128M
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped
# Prod stack optional overrides. The stack runs with NO env file:
# docker compose -f docker-compose.prod.yml up -d
# Every value below has a default inside docker-compose.prod.yml. Copy this file to
# .env.prod and keep only the lines you want to change, then start with:
# docker compose --env-file .env.prod -f docker-compose.prod.yml up -d
#
# TRAP: `docker compose` always loads the project-root `.env` for variable
# interpolation, even when `-f docker-compose.prod.yml` is passed. Inside a checkout that
# has a dev root `.env`, pass --env-file .env.prod (or run from another directory), or the
# stack silently inherits the dev stack's WEB_PORT and DATA_PATH and collides with it.
# Image tag to deploy (set by scripts/release.py; override manually
# for a specific version).
IMAGE_TAG=latest
# Secrets (POSTGRES_PASSWORD, BETTER_AUTH_SECRET, BROWSER_RUNTIME_TOKEN) are optional. A non-empty value wins and is
# written to ${DATA_PATH}/secrets/<name>/value. Empty = the `secrets` service keeps the existing file or generates one once.
# Back up ${DATA_PATH}/secrets. Rotate DB password: ALTER ROLE ... PASSWORD, delete secrets/postgres_password/value, set the
# new value here, `up -d`. Rotate the others: delete the file (or set the env), `up -d`; a new BETTER_AUTH_SECRET signs everyone out.
# Database
POSTGRES_USER=albatross
POSTGRES_PASSWORD=
POSTGRES_DB=albatross
# API
BETTER_AUTH_SECRET=
# Alert email: SMTP settings live in the DB (admin page /smtp-settings). The key below seals the
# stored SMTP password: base64 of exactly 32 bytes (openssl rand -base64 32). Optional: empty = the API
# generates one on first use into ${DATA_PATH}/api/settings.key and keeps it (back that file up).
SETTINGS_ENCRYPTION_KEY=
# Browser runtime (interactive link sessions + the display WebSocket relay).
# Shared secret between api/scraper and the runtime (optional, see Secrets above).
BROWSER_RUNTIME_TOKEN=
# Web
WEB_PORT=43280
# Data paths (bind mounts for persistent state) — kept separate from the dev
# stack's ./data so the two can run side by side without corrupting a shared
# Postgres data directory.
DATA_PATH=./data-prod
Content type
Image
Digest
sha256:852a73dcf…
Size
180.8 MB
Last updated
8 days ago
docker pull jhangyu/albatross-api