203 lines
10 KiB
Markdown
203 lines
10 KiB
Markdown
# Docker
|
|
|
|
RenewCMS runs as a container. The image is published to the registry at `git.floriansylvain.fr/fsylvain/renewcms`, so site operators pull it and never build from source; it can also be built from this repository. The image holds only the single binary (SPA embedded) on Alpine, runs as a non-root user (uid 10001) and keeps all state on one volume mounted at `/data`. It reuses the configuration contract described in [configuration.md](configuration.md); there are no Docker-specific variables.
|
|
|
|
Requirements: Docker with Compose v2. [task](https://taskfile.dev) is optional for operators and needed to build from source or to publish. Building needs no local Go or Bun. The image build bundles the SPA with `vite build` only; the `vue-tsc` type-check is not run inside the image (it does not work under the Bun-only build image), so keep running `bun run type-check` locally.
|
|
|
|
## Quick start
|
|
|
|
1. Copy the example env file and edit it:
|
|
|
|
```sh
|
|
cp docker.env.example docker.env
|
|
```
|
|
|
|
Set `PUBLIC_URL`, the `SMTP_*` values and a generated secret (`openssl rand -hex 32`) for `JWT_SECRET`. `docker.env` is git-ignored and excluded from the build context, so it never ends up in an image. Never commit it.
|
|
|
|
2. Pull and start the published image:
|
|
|
|
```sh
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
`docker-compose.yml` uses `image: ${RENEWCMS_IMAGE:-git.floriansylvain.fr/fsylvain/renewcms:beta}`. The default tag is `beta` because `latest` does not exist until the first stable release (the default changes to `latest` then). Set `RENEWCMS_IMAGE` in your shell or in a `.env` file next to the compose file to pin another tag, for example `RENEWCMS_IMAGE=git.floriansylvain.fr/fsylvain/renewcms:v2.9.0-beta`.
|
|
|
|
To build from the local sources instead, see [Building from source](#building-from-source).
|
|
|
|
3. Check it: `curl http://localhost:8080/healthz` returns `ok`, and `http://localhost:8080/` serves the SPA.
|
|
|
|
Other tasks: `task docker:pull`, `task docker:upgrade` (pull, then `docker compose up -d`), `task docker:up` (local build, see below), `task docker:build` (image only, tagged with the `git describe` version and `latest`), `task docker:run` (one-off foreground run on the volume `renewcms-data`, without Compose), `task docker:logs` and `task docker:down`.
|
|
|
|
If a required variable is missing the container exits non-zero and the log names it, for example `Environment variable JWT_SECRET is not set`.
|
|
|
|
### Local trial over plain HTTP
|
|
|
|
`ENVIRONMENT` defaults to `production`, which makes the auth cookie `Secure`: browsers do not store it over `http://`, so login appears to fail. For a local trial on `http://localhost:8080`, set `ENVIRONMENT=development` and `PUBLIC_URL=http://localhost:8080` in `docker.env`. Use `production` only behind HTTPS.
|
|
|
|
## Building from source
|
|
|
|
`docker-compose.build.yml` adds the `build:` section and tags the result `renewcms:${VERSION:-dev}`. Use it as an override:
|
|
|
|
```sh
|
|
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --build
|
|
```
|
|
|
|
`task docker:up` does the same and passes the `git describe` version as `VERSION`. To check what Compose resolves, run `docker compose config`: without the override it shows the registry image and no `build` section, with it the `build` section is present.
|
|
|
|
## Image tags
|
|
|
|
| Release | Tags pushed |
|
|
|---|---|
|
|
| `vX.Y.Z` (stable) | `vX.Y.Z`, `X.Y.Z`, `X.Y`, `latest` |
|
|
| `vX.Y.Z-<pre>` (for example `v2.9.0-beta`, `v2.9.0-rc.1`) | `vX.Y.Z-<pre>`, `X.Y.Z-<pre>`, and the moving tag `<pre-name>` (`beta`, `rc`) |
|
|
|
|
A pre-release never moves `latest`. Images are published for `linux/amd64` and `linux/arm64`. `renewcms --version` in the image prints the exact release tag. Images are not signed; the Ed25519 key signs the release binaries only.
|
|
|
|
## Environment
|
|
|
|
Compose reads `docker.env` through `env_file` and sets `DB_FILE=/data/renewcms.db` and `UPLOAD_DIR=/data/uploads` itself. Everything in [configuration.md](configuration.md) can go in `docker.env`. To publish another host port, change the left side of `ports` in `docker-compose.yml`; keep `PORT` at 8080 unless you also change the healthcheck.
|
|
|
|
The container receives SIGTERM on stop, and RenewCMS shuts down gracefully within 10 seconds. Compose uses `stop_grace_period: 15s`. The healthcheck calls `/healthz` inside the container.
|
|
|
|
## Data volume
|
|
|
|
State lives on the named volume `renewcms-data` (Compose prefixes it with the project name, usually `renewcms_renewcms-data`; check with `docker volume ls`). It holds the SQLite database, uploads, and update records.
|
|
|
|
- `docker compose down` removes the container and keeps the volume. The data survives and `docker compose up -d` brings it back.
|
|
- **`docker compose down -v` deletes the volume and all data.** The next start is an empty instance.
|
|
|
|
### Backup
|
|
|
|
Replace `VOLUME` with the real volume name. The image has no `sqlite3`, so stop the service for a consistent copy:
|
|
|
|
```sh
|
|
docker compose stop
|
|
docker run --rm -v VOLUME:/data -v "$PWD":/backup alpine tar czf /backup/renewcms-backup.tar.gz -C /data .
|
|
docker compose start
|
|
```
|
|
|
|
### Restore into a fresh volume
|
|
|
|
```sh
|
|
docker compose down -v
|
|
docker volume create VOLUME
|
|
docker run --rm -v VOLUME:/data -v "$PWD":/backup alpine sh -c "tar xzf /backup/renewcms-backup.tar.gz -C /data && chown -R 10001:10001 /data"
|
|
docker compose up -d
|
|
```
|
|
|
|
## Reverse proxy and TLS
|
|
|
|
With `ENVIRONMENT=production` the site must be served over HTTPS, so put a reverse proxy in front and set `PUBLIC_URL` to the public `https://` address. Bind the published port to the host only when the proxy runs on the same host, by changing `ports` to `"127.0.0.1:8080:8080"`.
|
|
|
|
Caddy on the host:
|
|
|
|
```caddyfile
|
|
cms.example.com {
|
|
reverse_proxy 127.0.0.1:8080 {
|
|
header_up X-Real-IP {remote_host}
|
|
}
|
|
}
|
|
```
|
|
|
|
nginx on the host:
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl;
|
|
server_name cms.example.com;
|
|
client_max_body_size 20m;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8080;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
}
|
|
}
|
|
```
|
|
|
|
Set `TRUSTED_PROXIES` to the proxy address as the container sees it, otherwise the login rate limiter ignores `X-Real-IP` and counts every client as the proxy. With a proxy on the host and the default bridge network, that is the bridge gateway (check with `docker network inspect`, often `172.17.0.1` or the Compose network gateway). With a proxy container on the same Compose network, it is that container's IP. The value is a semicolon-separated list of exact IPs, no CIDR. See [deployment.md](deployment.md#4-put-https-in-front).
|
|
|
|
## SMTP
|
|
|
|
RenewCMS sends registration and password-reset mail through the `SMTP_*` variables. Use your provider's host and port in `docker.env`.
|
|
|
|
To use a mail server running on the Docker host, set `SMTP_HOST=host.docker.internal`. Docker Desktop resolves that name. On Linux add the mapping to the service in `docker-compose.yml`:
|
|
|
|
```yaml
|
|
extra_hosts:
|
|
- "host.docker.internal:host-gateway"
|
|
```
|
|
|
|
The host MTA must listen on an address reachable from the container, not only on `127.0.0.1`.
|
|
|
|
If SMTP is unreachable, the server still starts. Only the actions that send mail (registration, password reset, invitations) fail, and the error appears in `docker compose logs renewcms`.
|
|
|
|
## Upgrading
|
|
|
|
1. Back up the volume (see above).
|
|
2. Pull the new image and recreate the container on the same volume:
|
|
|
|
```sh
|
|
docker compose pull
|
|
docker compose up -d
|
|
```
|
|
|
|
`task docker:upgrade` runs both. The schema is migrated at startup, and there is no down-migration.
|
|
3. Check `docker compose logs renewcms` for the new version line, or `docker run --rm git.floriansylvain.fr/fsylvain/renewcms:beta --version`. The owner's Settings page also shows the running version.
|
|
|
|
To roll back, set `RENEWCMS_IMAGE` to the previous tag, run `docker compose up -d`, and restore the backup if the schema changed.
|
|
|
|
If you built from source, upgrade with `git pull` and `task docker:up` instead.
|
|
|
|
## Maintainer: publishing
|
|
|
|
Publishing is a manual, maintainer-run task, like `task release`. There is no CI pipeline.
|
|
|
|
1. Tag the release and keep the work tree clean:
|
|
|
|
```sh
|
|
git tag v2.9.0-beta
|
|
```
|
|
|
|
2. Log in once to the registry. The task never reads or stores credentials:
|
|
|
|
```sh
|
|
docker login git.floriansylvain.fr
|
|
```
|
|
|
|
3. Make sure a buildx builder can build both platforms. Check with `docker buildx ls`. With the containerd image store (the Docker Desktop default) the default builder already works. Otherwise create one, and on a Linux host install the QEMU emulators:
|
|
|
|
```sh
|
|
docker buildx create --use
|
|
docker run --privileged --rm tonistiigi/binfmt --install arm64
|
|
```
|
|
|
|
4. Publish:
|
|
|
|
```sh
|
|
task docker:publish
|
|
```
|
|
|
|
`REGISTRY_IMAGE` defaults to `git.floriansylvain.fr/fsylvain/renewcms` and `VERSION` to `git describe --tags`. The task refuses to run, before building anything, when `REGISTRY_IMAGE` is empty, when `VERSION` is not an exact tag `vX.Y.Z` or `vX.Y.Z-<pre>` on HEAD, when the work tree is dirty, or when the builder cannot build `linux/amd64` and `linux/arm64`. It prints the tags it pushes.
|
|
|
|
### Dry run
|
|
|
|
```sh
|
|
task docker:publish -- --dry-run
|
|
task docker:publish VERSION=v2.9.0-rc.1 -- --dry-run
|
|
```
|
|
|
|
The dry run builds for the host platform only, loads the image into the local Docker, pushes nothing and needs no login. It prints the tags a real run would push. A `VERSION` override is accepted only with `--dry-run`, and a dirty tree is allowed there. Remove the loaded images afterwards with `docker rmi`.
|
|
|
|
### After pushing
|
|
|
|
- `docker buildx imagetools inspect git.floriansylvain.fr/fsylvain/renewcms:<tag>` lists `linux/amd64` and `linux/arm64`.
|
|
- The moving tag (`beta`) points at the new digest, and `latest` did not move for a pre-release.
|
|
- On a clean machine, `docker compose pull && docker compose up -d` starts the new image and Settings shows the new version.
|
|
|
|
## Limitations
|
|
|
|
Browser updates are unsupported in Docker (they need Linux with systemd, and a binary replaced inside a container is lost when it is recreated). The image and `docker-compose.yml` set `RENEWCMS_CONTAINER=true`, so the owner's Settings page shows when a newer version exists and tells them to pull a new image: `docker compose pull && docker compose up -d`. There is no Update or Restart button. Upgrade by pulling the image as above, or by rebuilding it if you build from source.
|