26 KiB
Bamort Infrastructure Findings
Generated: 2026-03-13
Scope: Docker infrastructure, scripts, networking, persistence, dev/prod differences
Table of Contents
- Docker Compose Configurations
- Dockerfiles
- Nginx Configuration
- Scripts
- Database Initialization
- Network Architecture
- Data Persistence
- Development vs Production Differences
1. Docker Compose Configurations
1.1 docker/docker-compose.dev.yml — Development Stack
Compose project name: bamort (set via COMPOSE_PROJECT_NAME env var)
Service: backend-dev
| Property | Value |
|---|---|
| Container name | bamort-backend-dev |
| Build context | ../backend |
| Dockerfile | ../docker/Dockerfile.backend.dev |
| Host port → Container port | 8180 → 8180 |
| Working directory | /app |
| Restart policy | unless-stopped |
| Depends on | mariadb-dev (condition: service_healthy) |
Environment variables:
GO_ENV=development
CGO_ENABLED=1
DATABASE_TYPE=${DATABASE_TYPE:-mysql}
DATABASE_URL=${DATABASE_URL:-<user>:<pass>@tcp(mariadb-dev:3306)/<db>?charset=utf8mb4&parseTime=True&loc=Local}
API_PORT=${API_PORT:-8180}
TEMPLATES_DIR=${TEMPLATES_DIR:-./templatesx}
EXPORT_TEMP_DIR=${EXPORT_TEMP_DIR:-./export_tempx}
GIT_COMMIT=${GIT_COMMIT:-unknown}
Note:
TEMPLATES_DIRdefaults to./templatesx(with trailing 'x') in Docker — this means the container uses the source-mounted../backend:/appvolume and the actual templates directory at/app/templates.
Volumes:
../backend:/app— full backend source mounted for live-reload via Airgo-mod-cache:/go/pkg/mod— named volume to cache Go module downloads
Service: frontend-dev
| Property | Value |
|---|---|
| Container name | bamort-frontend-dev |
| Build context | ../frontend |
| Dockerfile | ../docker/Dockerfile.frontend.dev |
| Host port → Container port | 5173 → 5173 |
| Restart policy | unless-stopped |
| Depends on | backend-dev |
Environment variables:
NODE_ENV=development
VITE_API_URL=${API_URL:-http://192.168.0.1:8180}
VITE_BASE_URL=${BASE_URL:-http://bamort.trokan.de}
VITE_API_PORT=${API_PORT:-8180}
The default
VITE_API_URLpoints to192.168.0.1— a LAN IP. In practice this is overridden via.env.devtohttp://localhost:8180.
Volumes:
../frontend:/app— full frontend source mounted for Vite HMR/app/node_modules— anonymous volume to prevent host from overwriting container's installednode_modules
Service: mariadb-dev
| Property | Value |
|---|---|
| Container name | bamort-mariadb-dev |
| Image | mariadb:11.4 |
| Host port → Container port | 3306 → 3306 |
| Restart policy | unless-stopped |
Environment variables:
MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD:-secure_root_password}
MARIADB_DATABASE=${MARIADB_DATABASE:-bamort}
MARIADB_USER=${MARIADB_USER:-bamort}
MARIADB_PASSWORD=${MARIADB_PASSWORD:-secure_user_password}
MARIADB_CHARSET=utf8mb4
MARIADB_COLLATION=utf8mb4_unicode_ci
Volumes:
./bamort-db-dev:/var/lib/mysql— persistent database files stored indocker/bamort-db-dev/./init-db:/docker-entrypoint-initdb.d— SQL initialization scripts (run only on first container creation)
Health check:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
start_period: 10s
timeout: 5s
retries: 3
Service: phpmyadmin-dev
| Property | Value |
|---|---|
| Container name | bamort-phpmyadmin-dev |
| Image | phpmyadmin/phpmyadmin:5.2 |
| Host port → Container port | 8081 → 80 |
| Restart policy | unless-stopped |
| Depends on | mariadb-dev (condition: service_healthy) |
Environment variables:
PMA_HOST=mariadb-dev
PMA_PORT=3306
PMA_USER=root
PMA_PASSWORD=${MARIADB_ROOT_PASSWORD:-secure_root_password}
MYSQL_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD:-secure_root_password}
PMA_ARBITRARY=1
Named volumes declared:
volumes:
go-mod-cache:
1.2 docker/docker-compose.yml — Production Stack
Service: backend
| Property | Value |
|---|---|
| Container name | bamort-backend |
| Build context | ../backend |
| Dockerfile | ../docker/Dockerfile.backend |
| Host port → Container port | 8182 → 8180 |
| Working directory | /app |
| Restart policy | unless-stopped |
| Depends on | mariadb (condition: service_healthy) |
Environment variables:
GO_ENV=production
CGO_ENABLED=1
DATABASE_TYPE=${DATABASE_TYPE:-mysql}
DATABASE_URL=<user>:<pass>@tcp(mariadb:3306)/<db>?charset=utf8mb4&parseTime=True&loc=Local
BASE_URL=${BASE_URL:-https://bamort.trokan.de}
API_PORT=${API_PORT:-8180}
Note:
DATABASE_URLin production is NOT wrapped in${}with a fallback — it's constructed inline, which means any unset env vars will result in empty credentials.
Volumes:
./templates:/app/templates— mounts host templates directory (no source code mounted)
Service: frontend
| Property | Value |
|---|---|
| Container name | bamort-frontend |
| Build context | ../frontend |
| Dockerfile | ../docker/Dockerfile.frontend |
| Host port → Container port | 8181 → 80 |
| Restart policy | unless-stopped |
| Depends on | backend |
Build args (baked into the image at build time):
VITE_API_URL: ${API_URL:-https://bamort-api.trokan.de}
VITE_BASE_URL: ${BASE_URL:-https://bamort.trokan.de}
VITE_API_PORT: ${API_PORT:-443}
These are compile-time args embedded into the Vite bundle — runtime
environment:vars have no effect on the built static bundle.
Environment variables (runtime — informational only):
NODE_ENV=production
VITE_API_URL=${API_URL:-https://bamort.trokan.de:8180}
VITE_BASE_URL=${BASE_URL:-https://bamort.trokan.de}
VITE_API_PORT=${API_PORT:-8180}
Service: mariadb
| Property | Value |
|---|---|
| Container name | bamort-mariadb |
| Image | mariadb:11.4 |
| Host port | NOT EXPOSED (ports section commented out) |
| Restart policy | unless-stopped |
Environment variables: Same as dev Volumes:
./bamort-db:/var/lib/mysql— persistent data indocker/bamort-db/./init-db:/docker-entrypoint-initdb.d
Health check: Same, but with longer timeouts (start_period: 20s, timeout: 10s, retries: 3)
Service: phpmyadmin (COMMENTED OUT in production)
The service definition exists in the file but is fully commented out for security. Can be re-enabled via:
sed -i 's/^ # phpmyadmin:/ phpmyadmin:/' docker-compose.yml
docker-compose up -d phpmyadmin
If re-enabled: port 8081 → 80
2. Dockerfiles
2.1 docker/Dockerfile.backend — Production Backend (Multi-stage)
Stage 1: builder — golang:1.25-alpine
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache gcc musl-dev sqlite-dev
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -v -o server cmd/main.go
- Installs GCC (required for CGO), musl-dev, and sqlite-dev (CGO is enabled for SQLite support)
- Builds a static binary named
serverfromcmd/main.go
Stage 2: Runtime — alpine:3.23
FROM alpine:3.23
RUN apk add --no-cache \
chromium chromium-chromedriver \
nss freetype harfbuzz ca-certificates ttf-freefont
ENV CHROME_BIN=/usr/bin/chromium-browser \
CHROME_PATH=/usr/bin/chromium-browser
WORKDIR /app
COPY --from=builder /app/server /app
COPY --from=builder /app/templates /app/default_templates
EXPOSE 8180
CMD ["./server"]
- Installs Chromium for PDF rendering via chromedp
- Copies compiled binary and templates from builder stage
- Templates copied to
default_templates/(runtime templates come from volume mount./templates:/app/templates) - Result: minimal Alpine image with only Chromium and the binary
2.2 docker/Dockerfile.backend.dev — Development Backend (Single-stage)
Base: golang:1.25-alpine (single stage — no build separation)
FROM golang:1.25-alpine
RUN apk add --no-cache gcc musl-dev sqlite-dev
RUN apk add --no-cache chromium chromium-chromedriver nss freetype harfbuzz ca-certificates ttf-freefont
ENV CHROME_BIN=/usr/bin/chromium-browser \
CHROME_PATH=/usr/bin/chromium-browser
RUN go install github.com/air-verse/air@latest
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
EXPOSE 8180
CMD ["air", "-c", ".air.toml"]
Key differences from production:
- Includes the full Go toolchain (for recompilation on file changes)
- Installs
air(live-reload tool fromgithub.com/air-verse/air@latest) - Full source code is mounted at runtime via volume, not copied into image
- Starts with
air -c .air.tomlinstead of the compiled binary
Air configuration (.air.toml):
root = "."
tmp_dir = "tmp"
[build]
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/main.go"
delay = 1000 # 1 second delay before rebuild
exclude_dir = ["assets", "tmp", "vendor", "testdata", "uploads", "transfer/xporttemp", "export_temp"]
exclude_regex = ["_test.go"] # Test files excluded from watch
include_ext = ["go", "tpl", "tmpl", "html"]
log = "build-errors.log"
Air watches .go, .tpl, .tmpl, .html files, excludes test files, and rebuilds into ./tmp/main on any change.
2.3 docker/Dockerfile.frontend — Production Frontend (Multi-stage)
Stage 1: build — node:22-alpine
FROM node:22-alpine AS build
ARG VITE_API_URL
ARG VITE_BASE_URL
ARG VITE_API_PORT
ENV VITE_API_URL=$VITE_API_URL
ENV VITE_BASE_URL=$VITE_BASE_URL
ENV VITE_API_PORT=$VITE_API_PORT
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
- Accepts
VITE_*values as build ARGs → embedded into the static bundle by Vite - Runs
npm run buildwhich producesdist/
Stage 2: serve — nginx:alpine
FROM nginx:alpine
COPY --from=build /usr/src/app/dist /usr/share/nginx/html
COPY --from=build /usr/src/app/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
- Serves the static Vue bundle via Nginx
- Uses
frontend/nginx.conffor SPA routing (see section 3)
2.4 docker/Dockerfile.frontend.dev — Development Frontend (Single-stage)
Base: node:22-alpine
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
EXPOSE 5173
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
- No build step — Vite dev server handles hot module replacement (HMR)
--host 0.0.0.0binds Vite to all interfaces (required for Docker port exposure)- Full source mounted at
/appvia volume at runtime node_modulesprotected inside container via anonymous volume in compose
3. Nginx Configuration
Note: Both docker/nginx.conf and frontend/nginx.conf are identical in content. The frontend/nginx.conf is the one copied into the production Docker image.
Configuration (both files are the same):
server {
listen 80;
listen [::]:80;
server_name localhost;
# SPA routing: try files, fallback to index.html
location / {
try_files $uri $uri/ /index.html;
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Key behavior:
- SPA routing:
try_files $uri $uri/ /index.html— any URL that doesn't match a static file falls through toindex.html, enabling Vue Router to handle client-side navigation - No backend proxy: Nginx does NOT proxy
/apirequests to the backend. The frontend communicates with the backend API directly via theVITE_API_URLenvironment variable baked into the bundle (e.g.,https://bamort-api.trokan.de) - Static file serving: All files served from
/usr/share/nginx/html - No HTTPS: The Nginx container handles only HTTP on port 80; TLS termination is expected to happen at an external reverse proxy (e.g., Traefik, HAProxy, or a hosting provider)
- No API proxy block: Unlike many SPA setups, there is no
location /apiproxy directive — the API is on a separate domain/port
4. Scripts
4.1 docker/start-dev.sh
#!/bin/bash
# Verifies Docker is running
# Changes to docker/ directory
# Exports GIT_COMMIT=$(git rev-parse --short HEAD)
# Runs: docker-compose -f docker-compose.dev.yml down
# Runs: docker-compose -f docker-compose.dev.yml up --build -d
What it does:
- Checks
docker info— exits with error if Docker daemon is not running cds to the script's own directory (so paths in compose file work)- Exports current short git commit hash as
GIT_COMMIT - Stops any existing dev containers first (
down) - Rebuilds images and starts all dev services in detached mode (
up --build -d)
4.2 docker/stop-dev.sh
#!/bin/bash
# Changes to docker/ directory
# Runs: docker-compose -f docker-compose.dev.yml down
Stops and removes all dev containers (but preserves volumes and images).
4.3 docker/start-prd.sh
#!/bin/bash
# Verifies Docker is running
# Changes to docker/ directory
# Runs: docker-compose -f docker-compose.yml build (pre-build while old containers run)
# Runs: docker-compose -f docker-compose.yml down (stop old containers)
# Runs: docker-compose -f docker-compose.yml up --build -d
Key difference from dev: Runs build before down to minimize downtime. Old containers continue running while new images are being built, then a quick switch happens.
4.4 docker/stop-prd.sh
#!/bin/bash
# Changes to docker/ directory
# Runs: docker-compose -f docker-compose.yml down
Stops and removes production containers. Note: the script message says "Stopping Development Environment" — this is a copy-paste bug in the echo message.
4.5 backend/startserver.sh
This file is not a standard shell script — it is actually a .env-style configuration file that also contains the final command to start the server locally (outside Docker):
# Environment variables for BaMoRT development environment
ENVIRONMENT=development
DATABASE_TYPE=mysql
DATABASE_URL="bamort:bG4)efozrc@tcp(192.168.0.36:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local"
MARIADB_ROOT_PASSWORD=root_password_dev
MARIADB_PASSWORD="bG4)efozrc"
MARIADB_DATABASE=bamort
MARIADB_USER=bamort
API_URL="http://localhost:8180"
VITE_API_URL="http://localhost:8180"
API_PORT=8180
BASE_URL="http://localhost:5173"
TEMPLATES_DIR=./templates
EXPORT_TEMP_DIR=./export_temp
GIT_COMMIT=d0c177b
LOG_LEVEL=debug
CHROME_BIN="/usr/bin/chromium"
echo $DATABASE_URL
/home/de31a2/.local/bin/go/bin/go run ./cmd/main.go
Used as a local dev helper to set env vars and launch the Go backend directly (no Docker). Points to a MariaDB at 192.168.0.36:3306 (a LAN server).
Security note: Contains hardcoded credentials (
bG4)efozrcfor MariaDB user) — this is development-only but should be in.gitignore.
4.6 backend/transfer_sqlite_to_mariadb.sh
BACKEND_URL="http://localhost:8180"
ENDPOINT="/api/maintenance/transfer-sqlite-to-mariadb"
What it does:
- Optionally accepts argument
clearto wipe MariaDB data before transfer (prompts for confirmation) - Calls
POST http://localhost:8180/api/maintenance/transfer-sqlite-to-mariadb[?clear=true]viacurl - Displays JSON response (formatted via
jqif available) or raw output
Purpose: One-time data migration from the SQLite test database (testdata/prepared_test_data.db) to the MariaDB development database. The actual transfer logic is implemented in the backend's /maintenance module.
4.7 scripts/export-databases.sh
What it does:
- Loads environment variables from
./docker/.env.dev - SQLite export:
- Dumps
backend/testdata/prepared_test_data.dbtobackend/testdata/exports/sqlite_dump.sql - Exports each table as a CSV file:
backend/testdata/exports/sqlite_<table>.csv
- Dumps
- MariaDB export (requires
bamort-mariadb-devcontainer running):- Runs
mariadb-dumpinside the container →backend/testdata/exports/mysql_dump.sql - Exports each table as CSV:
backend/testdata/exports/mysql_<table>.csv
- Runs
- Lists exported files
Purpose: Side-by-side export of SQLite (test data) and MariaDB (dev data) for comparison/debugging during data migration. The TIMESTAMP variable is intentionally empty (left blank in the script), so files are overwritten on each run.
4.8 scripts/update-version.sh
Files updated by this script:
| File | What changes |
|---|---|
backend/appsystem/version.go |
const Version = "X.Y.Z" |
backend/VERSION.md |
## Current Version: X.Y.Z |
frontend/src/version.js |
export const VERSION = 'X.Y.Z' |
frontend/package.json |
"version": "X.Y.Z" |
frontend/VERSION.md |
## Current Version: X.Y.Z |
CLI flags:
-b <version> Set backend version explicitly
-f <version> Set frontend version explicitly
-n Auto-bump patch version on both (reads current, increments Z in X.Y.Z)
-c Create a git commit after version update
-t Create a git tag after commit
Tag strategy:
- If both versions are equal: creates single tag
vX.Y.Z - If they differ: creates
backend-vX.Y.Zandfrontend-vX.Y.Zseparately
Example workflows:
./scripts/update-version.sh -b 0.1.31 -f 0.2.0 # Set explicit versions
./scripts/update-version.sh -b 0.1.31 -c -t # Set + commit + tag
./scripts/update-version.sh -n -c # Auto-bump patch + commit
./scripts/update-version.sh -c -t # Commit + tag from current files
5. Database Initialization
Init directory: docker/init-db/
| File | Status | Description |
|---|---|---|
00-init.sql |
Active | Full schema + seed data (~3,872 lines) |
01-init.sql.bak |
Backup | Earlier version (user data focus) |
02-init.sql.bak |
Backup | Skill tables variant |
09-init.sql.bak |
Backup | Indexes and auto-increments |
00-init.sql — Active Initialization Script
Generated by phpMyAdmin from mariadb-dev:3306 on 2025-12-30.
Contains 78 CREATE TABLE + INSERT INTO statements covering the full database schema.
Tables created (sampled):
audit_log_entries— character field change historychar_bennies— character blessings/bennieschar_characteristics— physical appearance (eye color, hair, size, etc.)- Full game character schema (all character sub-tables)
Settings applied on init:
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
SET NAMES utf8mb4;
Character set: utf8mb4 with utf8mb4_unicode_ci collation throughout.
Execution timing: Scripts in init-db/ run only once — when the MariaDB container is created for the first time. If ./bamort-db-dev/ (dev) or ./bamort-db/ (prod) already contain data, init scripts are skipped.
6. Network Architecture
Development Network Topology
Host Machine
├── :5173 ──→ bamort-frontend-dev (Vite HMR, Vue SPA)
├── :8180 ──→ bamort-backend-dev (Go/Gin REST API, Air live-reload)
├── :3306 ──→ bamort-mariadb-dev (MariaDB 11.4)
└── :8081 ──→ bamort-phpmyadmin-dev (phpMyAdmin 5.2)
Docker Internal Network (docker-compose default bridge)
├── bamort-frontend-dev → bamort-backend-dev:8180 (via VITE_API_URL on host)
├── bamort-backend-dev → mariadb-dev:3306 (internal DNS resolution)
└── bamort-phpmyadmin-dev → mariadb-dev:3306 (internal DNS resolution)
Inter-container communication:
- Backend connects to MariaDB using the Docker service name
mariadb-devas hostname (resolved via Docker internal DNS) - Frontend in dev calls the backend via
VITE_API_URLwhich is set to the host's IP (e.g.,http://localhost:8180), not an internal Docker name — meaning the browser makes direct calls to the exposed host port, not through Docker networking - phpMyAdmin connects to
mariadb-dev:3306internally
Production Network Topology
Host Machine
├── :8181 ──→ bamort-frontend (Nginx serving built Vue SPA)
├── :8182 ──→ bamort-backend (Go/Gin REST API, compiled binary)
│ bamort-mariadb (NOT exposed — internal only)
│ [phpmyadmin] (disabled by default)
Docker Internal Network
├── bamort-frontend → bamort-backend [via external VITE_API_URL baked into bundle]
├── bamort-backend → mariadb:3306 (internal DNS)
└── bamort-mariadb (no host port binding — not accessible from outside)
External reverse proxy (implicit): The production setup is designed to sit behind an external reverse proxy (e.g., Nginx, Traefik, or HAProxy) that terminates TLS and routes:
bamort.trokan.de→ container port8181(frontend)bamort-api.trokan.de→ container port8182(backend API)
CORS Configuration
The backend reads FRONTEND_URL from config (defaults to http://localhost:5173) and uses it for CORS origin allow-listing. Production backend receives BASE_URL=https://bamort.trokan.de for this purpose.
7. Data Persistence
Volume Mounts
| Environment | Service | Host path | Container path | Purpose |
|---|---|---|---|---|
| Dev | MariaDB | docker/bamort-db-dev/ |
/var/lib/mysql |
Database files |
| Dev | Backend | ../backend/ |
/app |
Source code (live-reload) |
| Dev | Frontend | ../frontend/ |
/app |
Source code (HMR) |
| Dev | Backend | go-mod-cache (named) |
/go/pkg/mod |
Go module cache |
| Prod | MariaDB | docker/bamort-db/ |
/var/lib/mysql |
Database files |
| Prod | Backend | docker/templates/ |
/app/templates |
PDF templates |
Database Persistence Strategy
Dev: docker/bamort-db-dev/ — a host directory mount. Survives container restarts and recreations. Wiped only if you remove the directory manually or run docker-compose down -v (though -v only removes named volumes, not bind mounts).
Prod: docker/bamort-db/ — same strategy. MariaDB port is NOT exposed to the host, so direct external access requires docker exec.
SQLite (Test Data)
The backend also has a SQLite database at backend/testdata/prepared_test_data.db used for automated tests. This is not a Docker volume — it's part of the source tree.
Backup Strategy
scripts/export-databases.sh: Manual backup script that:
- Dumps SQLite test DB to SQL + per-table CSV files
- Dumps MariaDB dev DB to SQL + per-table CSV files
- Output to
backend/testdata/exports/
No automated backup: There is no cron job or automated backup mechanism in the Docker setup. Backups are manual only. The docker/bamort_20260123_v0.1.37.sql and docker/bamort.sql_backup files are manual one-off snapshots.
8. Development vs Production Differences
| Aspect | Development | Production |
|---|---|---|
| Backend image | Dockerfile.backend.dev — full Go toolchain + Air |
Dockerfile.backend — multi-stage, Alpine runtime only |
| Backend start | air -c .air.toml (live reloads on .go file changes) |
./server (pre-compiled binary) |
| Backend source | Volume-mounted from host (../backend:/app) |
Compiled into image at build time |
| Frontend image | Dockerfile.frontend.dev — Node + Vite dev server |
Dockerfile.frontend — multi-stage, Nginx serving dist/ |
| Frontend start | npm run dev -- --host 0.0.0.0 (Vite HMR) |
nginx -g 'daemon off;' |
| Frontend port | 5173 |
8181 (maps to container 80) |
| Backend port | 8180 |
8182 (maps to container 8180) |
| MariaDB port | 3306 (exposed to host) |
NOT exposed externally |
| phpMyAdmin | Enabled on port 8081 |
Disabled (commented out) |
| VITE_API_URL | Runtime env var (set in compose) | Build-time ARG (baked into bundle) |
| Template source | Mounted from ../backend volume (part of source) |
Mounted from docker/templates/ |
| DB data dir | docker/bamort-db-dev/ |
docker/bamort-db/ |
| Go env | GO_ENV=development |
GO_ENV=production |
| MariaDB hostname | mariadb-dev |
mariadb |
| Health check timing | start_period: 10s |
start_period: 20s |
| Image size | Large (Go toolchain + Chromium) | Smaller (Alpine + Chromium only) |
| Hot reload | Yes (Air for Go, Vite HMR for Vue) | No |
| Startup script | start-dev.sh (includes git commit export) |
start-prd.sh (pre-builds before stopping) |
Notable Configuration Quirks
-
TEMPLATES_DIR=./templatesxin dev compose — has a trailingxsuffix, which means Air/server falls back to default./templates. The actual templates come from the bind-mounted source directory. -
Production DATABASE_URL is constructed inline without
${}fallback syntax, meaning ifMARIADB_USERorMARIADB_PASSWORDare unset, the URL will contain empty strings rather than defaults. -
Frontend VITE_API_URL defaults differ between dev (
192.168.0.1:8180) and prod (bamort-api.trokan.de). The dev default is a LAN IP that only works in the author's local network — other developers must set their own.env.dev. -
No Docker network is explicitly defined — both compose files rely on Docker Compose's default bridge network where all services in the same file can reach each other by service name.
-
startserver.shis misleadingly named — it is actually a combined.envfile + startup command for running outside Docker, not a proper shell script header (no#!/bin/bash).