Document nginx vserver setup
This commit is contained in:
@@ -1,14 +1,18 @@
|
|||||||
# SSH host or alias from ~/.ssh/config.
|
# SSH host or alias from ~/.ssh/config.
|
||||||
REMOTE_HOST=myvserver
|
# This can also be a literal user@host value, for example root@myvserver.
|
||||||
|
REMOTE_HOST=root@myvserver
|
||||||
|
|
||||||
# Remote filesystem layout.
|
# Remote filesystem layout.
|
||||||
REMOTE_APP_DIR=/opt/rolemasterdb
|
REMOTE_APP_DIR=/root/docker/rolemasterdb
|
||||||
REMOTE_DATA_DIR=/opt/rolemasterdb/data
|
REMOTE_DATA_DIR=/root/docker/rolemasterdb/data
|
||||||
|
|
||||||
# Docker names on the remote host.
|
# Docker names on the remote host.
|
||||||
IMAGE_NAME=rolemasterdb
|
IMAGE_NAME=rolemasterdb
|
||||||
CONTAINER_NAME=rolemasterdb
|
CONTAINER_NAME=rolemasterdb
|
||||||
|
|
||||||
|
# Bind the container only on loopback so nginx is the public entrypoint.
|
||||||
|
HOST_BIND_ADDRESS=127.0.0.1
|
||||||
|
|
||||||
# Port mapping: host:container.
|
# Port mapping: host:container.
|
||||||
HOST_PORT=8080
|
HOST_PORT=8080
|
||||||
CONTAINER_PORT=8080
|
CONTAINER_PORT=8080
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
# Docker Deployment To A Linux VServer
|
# Docker Deployment To A Linux VServer
|
||||||
|
|
||||||
This repo now includes a deployment script for shipping `RolemasterDb.App` to a remote Linux server that you reach with:
|
This repo now includes a deployment script for shipping `RolemasterDb.App` to a remote Linux server that you reach as `root`, for example with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh myvserver
|
ssh myvserver
|
||||||
```
|
```
|
||||||
|
|
||||||
The script publishes the app locally, uploads a release bundle over `scp`, builds the Docker image on the server, and replaces the running container.
|
The intended public URL is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://rolemasterdb.franktovar.de
|
||||||
|
```
|
||||||
|
|
||||||
|
The script publishes the app locally, uploads a release bundle over `scp`, builds the Docker image on the server, and replaces the running container. Nginx remains a one-time manual setup on the host and proxies to the container over localhost.
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
@@ -19,10 +25,12 @@ The script publishes the app locally, uploads a release bundle over `scp`, build
|
|||||||
On the vserver:
|
On the vserver:
|
||||||
|
|
||||||
1. Install Docker.
|
1. Install Docker.
|
||||||
2. Make sure the SSH user can run `docker`, or set `REMOTE_USE_SUDO=1` in the deploy config.
|
2. Install nginx.
|
||||||
3. Open the host port you want to expose, for example `8080`.
|
3. Install a certificate tool such as `certbot` plus the nginx plugin if you want automated certificate provisioning.
|
||||||
|
4. Make sure `rolemasterdb.franktovar.de` resolves to this server. Your wildcard `A` and `AAAA` records already satisfy that.
|
||||||
|
|
||||||
The deploy script stores releases under `REMOTE_APP_DIR/releases/<timestamp>` and keeps the SQLite database in `REMOTE_DATA_DIR/rolemaster.db`.
|
The deploy script stores releases under `REMOTE_APP_DIR/releases/<timestamp>` and keeps the SQLite database in `REMOTE_DATA_DIR/rolemaster.db`.
|
||||||
|
The container should stay behind nginx, so the default deploy config binds it to `127.0.0.1:8080`.
|
||||||
|
|
||||||
## Local Setup
|
## Local Setup
|
||||||
|
|
||||||
@@ -34,13 +42,78 @@ cp deploy/vserver.env.example deploy/vserver.env
|
|||||||
|
|
||||||
Adjust the values in `deploy/vserver.env` as needed:
|
Adjust the values in `deploy/vserver.env` as needed:
|
||||||
|
|
||||||
- `REMOTE_HOST`: your SSH host or alias, for example `myvserver`
|
- `REMOTE_HOST`: your SSH host or alias, for example `root@myvserver`
|
||||||
- `REMOTE_APP_DIR`: base directory on the server, for example `/opt/rolemasterdb`
|
- `REMOTE_APP_DIR`: base directory on the server, default `/root/docker/rolemasterdb`
|
||||||
- `REMOTE_DATA_DIR`: persistent directory for the SQLite database
|
- `REMOTE_DATA_DIR`: persistent directory for the SQLite database
|
||||||
- `HOST_PORT`: public port on the vserver
|
- `HOST_BIND_ADDRESS`: default `127.0.0.1` so only nginx can reach the container
|
||||||
- `REMOTE_USE_SUDO`: set to `1` if remote Docker commands need `sudo`
|
- `HOST_PORT`: nginx upstream port on the host, default `8080`
|
||||||
|
- `REMOTE_USE_SUDO`: keep `0` when you deploy as `root`
|
||||||
- `REMOTE_ENV_FILE`: optional existing env file on the server to pass through to `docker run`
|
- `REMOTE_ENV_FILE`: optional existing env file on the server to pass through to `docker run`
|
||||||
|
|
||||||
|
## One-Time Server Setup
|
||||||
|
|
||||||
|
Create the base directories on the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh myvserver
|
||||||
|
mkdir -p /root/docker/rolemasterdb/data
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy once so the container exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/deploy-vserver.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Create an nginx site config at `/etc/nginx/sites-available/rolemasterdb.franktovar.de`:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name rolemasterdb.franktovar.de;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable it and reload nginx:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ln -s /etc/nginx/sites-available/rolemasterdb.franktovar.de /etc/nginx/sites-enabled/rolemasterdb.franktovar.de
|
||||||
|
nginx -t
|
||||||
|
systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
At this point `http://rolemasterdb.franktovar.de` should work.
|
||||||
|
|
||||||
|
## HTTPS Setup
|
||||||
|
|
||||||
|
If `certbot` and the nginx plugin are installed, request the certificate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
certbot --nginx -d rolemasterdb.franktovar.de
|
||||||
|
```
|
||||||
|
|
||||||
|
That should update the nginx config to serve HTTPS and normally also install an HTTP-to-HTTPS redirect. Verify the generated config and then reload nginx if needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nginx -t
|
||||||
|
systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, `https://rolemasterdb.franktovar.de` should terminate TLS in nginx and proxy traffic to `127.0.0.1:8080`.
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
@@ -61,6 +134,7 @@ Or point at a different config file:
|
|||||||
2. Precreates `publish/wwwroot/components/layout` and `publish/wwwroot/components/shared` before publish.
|
2. Precreates `publish/wwwroot/components/layout` and `publish/wwwroot/components/shared` before publish.
|
||||||
This is required because the current project otherwise fails to publish two JavaScript static assets.
|
This is required because the current project otherwise fails to publish two JavaScript static assets.
|
||||||
3. Bundles the publish output, `src/RolemasterDb.App/rolemaster.db`, and the runtime Dockerfile into `artifacts/deploy/<release-id>/`.
|
3. Bundles the publish output, `src/RolemasterDb.App/rolemaster.db`, and the runtime Dockerfile into `artifacts/deploy/<release-id>/`.
|
||||||
|
The publish output already includes `src/RolemasterDb.App/import-artifacts`, so those files are shipped as part of every release bundle.
|
||||||
4. Uploads the bundle to the vserver.
|
4. Uploads the bundle to the vserver.
|
||||||
5. Extracts the bundle on the vserver.
|
5. Extracts the bundle on the vserver.
|
||||||
6. Seeds `REMOTE_DATA_DIR/rolemaster.db` on first deploy only.
|
6. Seeds `REMOTE_DATA_DIR/rolemaster.db` on first deploy only.
|
||||||
@@ -69,7 +143,7 @@ Or point at a different config file:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
--restart unless-stopped
|
--restart unless-stopped
|
||||||
-p HOST_PORT:CONTAINER_PORT
|
-p HOST_BIND_ADDRESS:HOST_PORT:CONTAINER_PORT
|
||||||
-v REMOTE_DATA_DIR:/app/data
|
-v REMOTE_DATA_DIR:/app/data
|
||||||
ConnectionStrings__RolemasterDb=Data Source=/app/data/rolemaster.db
|
ConnectionStrings__RolemasterDb=Data Source=/app/data/rolemaster.db
|
||||||
```
|
```
|
||||||
@@ -81,7 +155,9 @@ Running the same deploy command again will:
|
|||||||
- publish a fresh release bundle
|
- publish a fresh release bundle
|
||||||
- upload a new timestamped release
|
- upload a new timestamped release
|
||||||
- keep the existing database in `REMOTE_DATA_DIR`
|
- keep the existing database in `REMOTE_DATA_DIR`
|
||||||
|
- keep the imported artifact files inside the image for that release
|
||||||
- rebuild the image and restart the container
|
- rebuild the image and restart the container
|
||||||
|
- leave the nginx config and TLS setup untouched
|
||||||
|
|
||||||
## Useful Remote Commands
|
## Useful Remote Commands
|
||||||
|
|
||||||
@@ -96,3 +172,10 @@ Follow logs:
|
|||||||
```bash
|
```bash
|
||||||
ssh myvserver docker logs -f rolemasterdb
|
ssh myvserver docker logs -f rolemasterdb
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Check nginx:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh myvserver nginx -t
|
||||||
|
ssh myvserver systemctl status nginx
|
||||||
|
```
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ REMOTE_APP_DIR="${REMOTE_APP_DIR:-/opt/rolemasterdb}"
|
|||||||
REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_APP_DIR/data}"
|
REMOTE_DATA_DIR="${REMOTE_DATA_DIR:-$REMOTE_APP_DIR/data}"
|
||||||
IMAGE_NAME="${IMAGE_NAME:-rolemasterdb}"
|
IMAGE_NAME="${IMAGE_NAME:-rolemasterdb}"
|
||||||
CONTAINER_NAME="${CONTAINER_NAME:-rolemasterdb}"
|
CONTAINER_NAME="${CONTAINER_NAME:-rolemasterdb}"
|
||||||
|
HOST_BIND_ADDRESS="${HOST_BIND_ADDRESS:-}"
|
||||||
HOST_PORT="${HOST_PORT:-8080}"
|
HOST_PORT="${HOST_PORT:-8080}"
|
||||||
CONTAINER_PORT="${CONTAINER_PORT:-8080}"
|
CONTAINER_PORT="${CONTAINER_PORT:-8080}"
|
||||||
REMOTE_USE_SUDO="${REMOTE_USE_SUDO:-0}"
|
REMOTE_USE_SUDO="${REMOTE_USE_SUDO:-0}"
|
||||||
@@ -89,6 +90,7 @@ ssh "$REMOTE_HOST" bash -s -- \
|
|||||||
"$REMOTE_DATA_DIR" \
|
"$REMOTE_DATA_DIR" \
|
||||||
"$IMAGE_NAME" \
|
"$IMAGE_NAME" \
|
||||||
"$CONTAINER_NAME" \
|
"$CONTAINER_NAME" \
|
||||||
|
"$HOST_BIND_ADDRESS" \
|
||||||
"$HOST_PORT" \
|
"$HOST_PORT" \
|
||||||
"$CONTAINER_PORT" \
|
"$CONTAINER_PORT" \
|
||||||
"$REMOTE_USE_SUDO" \
|
"$REMOTE_USE_SUDO" \
|
||||||
@@ -100,10 +102,11 @@ remote_app_dir="$2"
|
|||||||
remote_data_dir="$3"
|
remote_data_dir="$3"
|
||||||
image_name="$4"
|
image_name="$4"
|
||||||
container_name="$5"
|
container_name="$5"
|
||||||
host_port="$6"
|
host_bind_address="$6"
|
||||||
container_port="$7"
|
host_port="$7"
|
||||||
remote_use_sudo="$8"
|
container_port="$8"
|
||||||
remote_env_file="$9"
|
remote_use_sudo="$9"
|
||||||
|
remote_env_file="${10}"
|
||||||
|
|
||||||
release_dir="$remote_app_dir/releases/$release_id"
|
release_dir="$remote_app_dir/releases/$release_id"
|
||||||
tarball_path="$release_dir/release.tar.gz"
|
tarball_path="$release_dir/release.tar.gz"
|
||||||
@@ -132,13 +135,18 @@ docker_run_args=(
|
|||||||
-d
|
-d
|
||||||
--name "$container_name"
|
--name "$container_name"
|
||||||
--restart unless-stopped
|
--restart unless-stopped
|
||||||
-p "$host_port:$container_port"
|
|
||||||
-e "ASPNETCORE_ENVIRONMENT=Production"
|
-e "ASPNETCORE_ENVIRONMENT=Production"
|
||||||
-e "ASPNETCORE_URLS=http://+:$container_port"
|
-e "ASPNETCORE_URLS=http://+:$container_port"
|
||||||
-e "ConnectionStrings__RolemasterDb=Data Source=/app/data/rolemaster.db"
|
-e "ConnectionStrings__RolemasterDb=Data Source=/app/data/rolemaster.db"
|
||||||
-v "$remote_data_dir:/app/data"
|
-v "$remote_data_dir:/app/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if [[ -n "$host_bind_address" ]]; then
|
||||||
|
docker_run_args+=(-p "$host_bind_address:$host_port:$container_port")
|
||||||
|
else
|
||||||
|
docker_run_args+=(-p "$host_port:$container_port")
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n "$remote_env_file" ]]; then
|
if [[ -n "$remote_env_file" ]]; then
|
||||||
docker_run_args+=(--env-file "$remote_env_file")
|
docker_run_args+=(--env-file "$remote_env_file")
|
||||||
fi
|
fi
|
||||||
@@ -152,7 +160,11 @@ ln -sfn "$release_dir" "$remote_app_dir/current"
|
|||||||
echo "Deployment complete."
|
echo "Deployment complete."
|
||||||
echo "Release: $release_id"
|
echo "Release: $release_id"
|
||||||
echo "Container: $container_name"
|
echo "Container: $container_name"
|
||||||
echo "Host port: $host_port"
|
if [[ -n "$host_bind_address" ]]; then
|
||||||
|
echo "Published at: $host_bind_address:$host_port -> $container_port"
|
||||||
|
else
|
||||||
|
echo "Published at: $host_port -> $container_port"
|
||||||
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Local bundle ready at $tarball_path"
|
echo "Local bundle ready at $tarball_path"
|
||||||
|
|||||||
Reference in New Issue
Block a user