chore: simplify standalone Compose setup
This commit is contained in:
18
.env.example
18
.env.example
@@ -1,8 +1,16 @@
|
||||
# Exact public origin; use HTTPS behind your reverse proxy in production.
|
||||
# Public origin. Set your HTTPS origin when using a reverse proxy.
|
||||
SITE_URL=http://localhost:3000
|
||||
# Native development/maintenance database and uploads directory.
|
||||
PORT=3000
|
||||
# Set 0.0.0.0 to expose the port beyond this machine.
|
||||
BIND_ADDRESS=127.0.0.1
|
||||
# Native development only; Docker always stores data at /data.
|
||||
DATA_DIR=./data
|
||||
# Optional. If absent, initialization generates a secret in DATA_DIR/auth-secret.
|
||||
# These defaults preserve volumes from the previous production deployment.
|
||||
PORTFOLIO_DATA_VOLUME=portfolio_portfolio-data
|
||||
PORTFOLIO_CACHE_VOLUME=portfolio_next-image-cache
|
||||
# Optional; otherwise generated once and kept in the data volume.
|
||||
# BETTER_AUTH_SECRET=replace-with-at-least-32-random-characters
|
||||
# For compose.selfhost.yaml, directory mounted at /data:
|
||||
PORTFOLIO_DATA_DIR=./data
|
||||
# Override if your server needs a registry mirror.
|
||||
NPM_REGISTRY=https://registry.npmjs.org/
|
||||
# Optional existing Caddy network (Linux server path separator):
|
||||
# COMPOSE_FILE=compose.yaml:compose.caddy.yaml
|
||||
|
||||
138
SELF_HOSTING.md
138
SELF_HOSTING.md
@@ -32,42 +32,42 @@ them in a production container's environment after provisioning.
|
||||
|
||||
## Docker on your own server
|
||||
|
||||
The generic Compose file does not depend on the original private Caddy network.
|
||||
Set `SITE_URL` in `.env` to the exact public origin, including the scheme and any
|
||||
nonstandard port. Set `PORTFOLIO_DATA_DIR` to an absolute, local disk directory.
|
||||
The container runs as UID/GID 1001; this directory must be writable by that user.
|
||||
With Docker Engine and Compose v2 installed, clone/pull this repository, copy
|
||||
`.env.example` to `.env`, set your public `SITE_URL`, then run:
|
||||
|
||||
```sh
|
||||
mkdir -p /srv/portfolio/data
|
||||
sudo chown 1001:1001 /srv/portfolio/data
|
||||
# .env: SITE_URL=https://portfolio.example.com
|
||||
# .env: PORTFOLIO_DATA_DIR=/srv/portfolio/data
|
||||
docker compose -f compose.selfhost.yaml up -d --build
|
||||
docker compose -f compose.selfhost.yaml exec portfolio-app node maintenance.mjs create-owner
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Compose binds port 3000 to the host's loopback address. Point your existing HTTPS
|
||||
reverse proxy to `127.0.0.1:3000`. For a local trial, keep
|
||||
`SITE_URL=http://localhost:3000`. Set `PORT` to change the published host port and
|
||||
adjust `SITE_URL` accordingly. The proxy must replace incoming forwarded IP
|
||||
headers with the real client address so authentication rate limiting is reliable.
|
||||
The default named volumes are created with the required ownership automatically.
|
||||
The application initializes SQLite and its authentication secret, but **never
|
||||
creates an owner on startup**. When you want admin access, run:
|
||||
|
||||
The production image includes `maintenance.mjs`; it does not need TypeScript,
|
||||
Drizzle Kit, npm installs, or a compiler at runtime. Startup applies the committed
|
||||
SQL migrations and seeds samples only when the database is empty. `/api/health`
|
||||
checks database readiness without rendering the portfolio.
|
||||
```sh
|
||||
docker compose exec portfolio-app node maintenance.mjs create-user
|
||||
# create-owner remains an equivalent command.
|
||||
docker compose exec portfolio-app node maintenance.mjs reset-password
|
||||
```
|
||||
|
||||
The Docker build compiles libvips and Sharp from source for baseline CPUs. This
|
||||
supports VPS hosts that lack the features required by Sharp's prebuilt native
|
||||
and WebAssembly binaries. The first build takes longer; Docker caches the image
|
||||
libraries for later application changes. Image processing is checked before
|
||||
Next.js compilation and again in the final runtime image. When upgrading Sharp,
|
||||
keep the libvips source version in the Dockerfile at or above Sharp's minimum.
|
||||
The commands prompt for credentials. No owner credentials are needed in `.env`.
|
||||
Open `/admin` after creating the owner. For updates, pull and repeat the same
|
||||
Compose command. Never use `docker compose down -v` for data you intend to keep.
|
||||
|
||||
Use one application instance with a local filesystem. Do not mount the SQLite
|
||||
database over NFS/SMB, use an ephemeral serverless disk, or run multiple replicas
|
||||
against the same data volume. PostgreSQL would be the next step if those become
|
||||
requirements.
|
||||
The default port binds to localhost. Set `BIND_ADDRESS=0.0.0.0` to access it
|
||||
remotely, or point your existing HTTPS reverse proxy to `127.0.0.1:3000`.
|
||||
Set `PORT` and `SITE_URL` together when using a different port. The proxy should
|
||||
replace forwarded IP headers with the actual client address.
|
||||
|
||||
For the existing Caddy Docker network, set
|
||||
`COMPOSE_FILE=compose.yaml:compose.caddy.yaml` in the Linux server's `.env`.
|
||||
Caddy must already belong to `caddy_proxy` and proxy to `portfolio-app:3000`.
|
||||
Standalone installations do not need that network or override.
|
||||
|
||||
Use a single application instance and local storage. SQLite is not intended for
|
||||
multiple replicas or a network filesystem. Startup applies committed migrations.
|
||||
The image includes `maintenance.mjs`, so maintenance needs no npm installation.
|
||||
Baseline CPU support for image processing is retained; native compilation is
|
||||
cached separately and can make the first build slower.
|
||||
|
||||
## Persistent files and configuration
|
||||
|
||||
@@ -75,7 +75,8 @@ requirements.
|
||||
| --- | --- |
|
||||
| `SITE_URL` | Exact origin used for authentication and cross-site request checks |
|
||||
| `DATA_DIR` | Native runtime directory, default `./data`; Docker uses `/data` |
|
||||
| `PORTFOLIO_DATA_DIR` | Host bind mount for the generic Compose file |
|
||||
| `PORTFOLIO_DATA_VOLUME` | Persistent Docker volume name; preserve it on upgrades |
|
||||
| `PORTFOLIO_CACHE_VOLUME` | Docker image-cache volume name |
|
||||
| `BETTER_AUTH_SECRET` | Optional secret of at least 32 characters |
|
||||
| `portfolio.sqlite` | Content, owner account, and sessions |
|
||||
| `uploads/` | Validated images and PDF attachments |
|
||||
@@ -165,7 +166,7 @@ The target must be new and outside the data directory. In Docker, mount a host
|
||||
backup directory at `/backups` in an override file, then run:
|
||||
|
||||
```sh
|
||||
docker compose -f compose.selfhost.yaml -f compose.backup.yaml exec portfolio-app node maintenance.mjs backup /backups/before-upgrade
|
||||
docker compose -f compose.yaml -f compose.backup.yaml exec portfolio-app node maintenance.mjs backup /backups/before-upgrade
|
||||
```
|
||||
|
||||
Example `compose.backup.yaml`:
|
||||
@@ -199,35 +200,56 @@ remain. Stop all application and maintenance processes, remove only that lock
|
||||
file from the data directory, then restart. Do not remove it while a write or
|
||||
backup is running.
|
||||
|
||||
## Upgrades and the existing Gitea deployment
|
||||
## Upgrading an existing installation
|
||||
|
||||
Build and test a new image, back up, then replace the container using the same
|
||||
data volume. Never use `docker compose down -v` for an installation you intend
|
||||
to keep. Migrations are versioned under `migrations/`; generate new migrations
|
||||
with `npx drizzle-kit generate` during development and commit them.
|
||||
Keep your existing data volume: the default `portfolio_portfolio-data` matches
|
||||
the previous deployment with project name `portfolio`. If yours differs, set
|
||||
`PORTFOLIO_DATA_VOLUME` to its existing name **before** starting the new Compose
|
||||
configuration. Preserve the existing authentication secret, including any
|
||||
`BETTER_AUTH_SECRET` you previously supplied. Copy `SITE_URL` from `runtime.env`
|
||||
into `.env`. Enable the optional Caddy override when retaining that network.
|
||||
|
||||
The existing `compose.yaml` retains its `caddy_proxy` network and adds a named
|
||||
`portfolio-data` volume. Create `<DEPLOY_PATH>/runtime.env` containing the real
|
||||
`SITE_URL=https://your-domain.example`; the release script passes it to Compose.
|
||||
Keep this file private and outside version control. Existing deployments must
|
||||
explicitly import the original portfolio into the new data volume before editing.
|
||||
If you previously used the self-hosting bind mount, keep it through a local
|
||||
`compose.override.yaml` instead of accidentally starting an empty named volume:
|
||||
|
||||
Automatic rollback to an older image is disabled after a failed activation:
|
||||
startup may already have changed the database schema. To roll back, restore the
|
||||
pre-upgrade backup into a new volume/directory and activate the matching previous
|
||||
image. Retain both the failed data and the backup for diagnosis.
|
||||
|
||||
## Verification
|
||||
|
||||
```sh
|
||||
npm run test:content
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
npm run build
|
||||
npm test
|
||||
npm run test:e2e
|
||||
```yaml
|
||||
services:
|
||||
portfolio-app:
|
||||
volumes:
|
||||
- /absolute/path/to/existing/data:/data
|
||||
```
|
||||
|
||||
Browser tests provision a temporary owner in a temporary data directory. Their
|
||||
credentials are test fixtures, not production defaults. The optional container smoke test
|
||||
checks generic startup, owner commands, uploads, and persistence. Run `scripts/smoke-container.sh IMAGE_TAG` against the built image.
|
||||
Keep its existing UID/GID 1001 ownership. Do not copy a live SQLite database.
|
||||
Back up before upgrading. To migrate old `/images/...` references, mount your
|
||||
external private bundle read-only in the same override:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
portfolio-app:
|
||||
volumes:
|
||||
- /absolute/path/to/private-bundle:/import:ro
|
||||
```
|
||||
|
||||
Combine both mounts in one `volumes` list if using both. Build the new image,
|
||||
then run the migration against the existing volume before replacing the server:
|
||||
|
||||
```sh
|
||||
docker compose build
|
||||
docker compose run --rm --no-deps portfolio-app node maintenance.mjs migrate-legacy-media /import
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This is a one-time content migration, not a requirement for fresh installations.
|
||||
If startup fails after a schema migration, restore a pre-upgrade backup with the
|
||||
matching older image. Do not automatically downgrade the image over newer data.
|
||||
See `DEPLOYMENT.md` for the simplified Gitea workflow.
|
||||
|
||||
## Build checks
|
||||
|
||||
```sh
|
||||
npm run typecheck
|
||||
npm run build
|
||||
```
|
||||
|
||||
The production build includes TypeScript validation. Browser tests and other
|
||||
optional test suites are not part of deployment.
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
services:
|
||||
portfolio-app:
|
||||
image: amiirkhl-portfolio:latest
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: runtime
|
||||
10
compose.caddy.yaml
Normal file
10
compose.caddy.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# Optional: COMPOSE_FILE=compose.yaml:compose.caddy.yaml in the server's .env.
|
||||
services:
|
||||
portfolio-app:
|
||||
networks:
|
||||
- default
|
||||
- caddy_proxy
|
||||
networks:
|
||||
caddy_proxy:
|
||||
external: true
|
||||
name: caddy_proxy
|
||||
@@ -1,15 +0,0 @@
|
||||
services:
|
||||
portfolio-app:
|
||||
build: .
|
||||
image: portfolio-builder:local
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:${PORT:-3000}:3000"
|
||||
environment:
|
||||
SITE_URL: ${SITE_URL:-http://localhost:3000}
|
||||
DATA_DIR: /data
|
||||
volumes:
|
||||
- ${PORTFOLIO_DATA_DIR:-./data}:/data
|
||||
- next-image-cache:/app/.next/cache
|
||||
volumes:
|
||||
next-image-cache:
|
||||
40
compose.yaml
40
compose.yaml
@@ -1,34 +1,24 @@
|
||||
name: portfolio
|
||||
services:
|
||||
portfolio-app:
|
||||
image: ${PORTFOLIO_IMAGE:-amiirkhl-portfolio:latest}
|
||||
container_name: portfolio-app
|
||||
image: portfolio:local
|
||||
build:
|
||||
context: .
|
||||
target: runtime
|
||||
args:
|
||||
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmjs.org/}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${BIND_ADDRESS:-127.0.0.1}:${PORT:-3000}:3000"
|
||||
environment:
|
||||
SITE_URL: ${SITE_URL:-https://portfolio.example.com}
|
||||
SITE_URL: ${SITE_URL:-http://localhost:3000}
|
||||
DATA_DIR: /data
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"node",
|
||||
"-e",
|
||||
"fetch('http://127.0.0.1:3000/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))",
|
||||
]
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
start_period: 3s
|
||||
retries: 30
|
||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-}
|
||||
volumes:
|
||||
- next-image-cache:/app/.next/cache
|
||||
- portfolio-data:/data
|
||||
networks:
|
||||
- caddy_proxy
|
||||
|
||||
networks:
|
||||
caddy_proxy:
|
||||
external: true
|
||||
name: caddy_proxy
|
||||
|
||||
- next-image-cache:/app/.next/cache
|
||||
volumes:
|
||||
next-image-cache:
|
||||
portfolio-data:
|
||||
name: ${PORTFOLIO_DATA_VOLUME:-portfolio_portfolio-data}
|
||||
next-image-cache:
|
||||
name: ${PORTFOLIO_CACHE_VOLUME:-portfolio_next-image-cache}
|
||||
|
||||
Reference in New Issue
Block a user