Files
wehub-resource-sync 75f3dd141c
CI and Release / e2e-verification-agent (push) Blocked by required conditions
CI and Release / test-backend (push) Blocked by required conditions
CI and Release / build-dev-image (push) Waiting to run
CI and Release / push-dev-image (push) Blocked by required conditions
CI and Release / build-image (push) Blocked by required conditions
CI and Release / build-verification-image (push) Blocked by required conditions
CI and Release / determine-version (push) Blocked by required conditions
CI and Release / push-image (push) Blocked by required conditions
CI and Release / push-verification-image (12) (push) Blocked by required conditions
CI and Release / push-verification-image (18) (push) Blocked by required conditions
CI and Release / release (push) Blocked by required conditions
CI and Release / publish-helm-chart (push) Blocked by required conditions
CI and Release / lint-backend (push) Waiting to run
CI and Release / lint-frontend (push) Waiting to run
CI and Release / dockerfile-scan (push) Waiting to run
CI and Release / test-frontend (push) Blocked by required conditions
CI and Release / lint-verification-agent (push) Waiting to run
CI and Release / test-verification-agent (push) Blocked by required conditions
CI and Release / push-verification-image (13) (push) Blocked by required conditions
CI and Release / push-verification-image (14) (push) Blocked by required conditions
CI and Release / push-verification-image (15) (push) Blocked by required conditions
CI and Release / push-verification-image (16) (push) Blocked by required conditions
CI and Release / push-verification-image (17) (push) Blocked by required conditions
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

444 lines
16 KiB
Docker

# ========= BUILD FRONTEND =========
FROM --platform=$BUILDPLATFORM node:24-alpine AS frontend-build
WORKDIR /frontend
# Add version for the frontend build
ARG APP_VERSION=dev
ENV VITE_APP_VERSION=$APP_VERSION
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY frontend/ ./
# Copy .env file (with fallback to .env.production.example)
RUN if [ ! -f .env ]; then \
if [ -f .env.production.example ]; then \
cp .env.production.example .env; \
fi; \
fi
RUN pnpm build
# ========= BUILD BACKEND =========
# Backend build stage
FROM --platform=$BUILDPLATFORM golang:1.26.3 AS backend-build
# Make TARGET args available early so tools built here match the final image arch
ARG TARGETOS
ARG TARGETARCH
# Install Go public tools needed in runtime. Use `go build` for goose so the
# binary is compiled for the target architecture instead of downloading a
# prebuilt binary which may have the wrong architecture (causes exec format
# errors on ARM).
RUN git clone --depth 1 --branch v3.27.1 https://github.com/pressly/goose.git /tmp/goose && \
cd /tmp/goose/cmd/goose && \
GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
go build -o /usr/local/bin/goose . && \
rm -rf /tmp/goose
RUN go install github.com/swaggo/swag/cmd/swag@v1.16.4
# Set working directory
WORKDIR /app
# Install Go dependencies
COPY backend/go.mod backend/go.sum ./
RUN go mod download
# Create required directories for embedding
RUN mkdir -p /app/ui/build
# Copy frontend build output for embedding
COPY --from=frontend-build /frontend/dist /app/ui/build
# Generate Swagger documentation
COPY backend/ ./
RUN swag init -d . -g cmd/main.go -o swagger
# Compile the backend
ARG TARGETOS
ARG TARGETARCH
ARG TARGETVARIANT
RUN CGO_ENABLED=0 \
GOOS=$TARGETOS \
GOARCH=$TARGETARCH \
go build -o /app/main ./cmd
# ========= BUILD VERIFICATION AGENT =========
FROM --platform=$BUILDPLATFORM golang:1.26.3 AS verification-agent-build
ARG APP_VERSION=dev
WORKDIR /agent
COPY agent/verification/go.mod agent/verification/go.sum ./
RUN go mod download
COPY agent/verification/ ./
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags "-X main.Version=${APP_VERSION}" \
-o /verification-agent-binaries/databasus-verification-agent-linux-amd64 ./cmd
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \
go build -ldflags "-X main.Version=${APP_VERSION}" \
-o /verification-agent-binaries/databasus-verification-agent-linux-arm64 ./cmd
# ========= RUNTIME =========
# In this final image we ship only what the running app actually needs:
# - Build-only tooling (compilers, codegen, key fetchers) stays in the earlier
# stages above and never reaches here.
# - Anything pulled in for a single build step is purged within the same layer.
# - We keep this tight because every extra binary widens the attack surface and
# adds another CVE to track.
FROM debian:bookworm-slim
# Add version metadata to runtime image
ARG APP_VERSION=dev
ARG TARGETARCH
LABEL org.opencontainers.image.version=$APP_VERSION
ENV APP_VERSION=$APP_VERSION
ENV CONTAINER_ARCH=$TARGETARCH
# Set production mode for Docker containers
ENV ENV_MODE=production
# ========= Install all apt packages in a single layer =========
# Base packages + PostgreSQL 17 (pgdg repo) + Valkey (greensec repo) + rclone, in
# one RUN to minimise layer count and cache-export overhead.
#
# - wget: build-only — fetches the repo signing keys, then purged at the end of
# this RUN (see the "minimal attack surface" note on the runtime stage above).
# - Repo keys: scoped signed-by keyrings, not the deprecated global apt-key trust
# store, so a compromised repo key cannot vouch for any other repository.
# - Codename: hardcoded "bookworm" (base image is pinned), so no lsb-release.
# - Valkey: bound to localhost only — never exposed outside the container.
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates gosu rclone \
libncurses5 libncurses6 libmariadb3 libgnutls30 \
wget; \
wget -qO /usr/share/keyrings/pgdg.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc; \
echo "deb [signed-by=/usr/share/keyrings/pgdg.asc] http://apt.postgresql.org/pub/repos/apt bookworm-pgdg main" \
> /etc/apt/sources.list.d/pgdg.list; \
wget -qO /usr/share/keyrings/greensec.github.io-valkey-debian.key \
https://greensec.github.io/valkey-debian/public.key; \
echo "deb [signed-by=/usr/share/keyrings/greensec.github.io-valkey-debian.key] https://greensec.github.io/valkey-debian/repo bookworm main" \
> /etc/apt/sources.list.d/valkey-debian.list; \
apt-get update; \
apt-get install -y --no-install-recommends postgresql-17 valkey; \
apt-get purge -y --auto-remove wget; \
rm -rf /var/lib/apt/lists/*
# ========= Pre-built DB client binaries (PG, MySQL, MariaDB, MongoDB) =========
# All client tools live under /app/assets/tools/<arch>/ — the backend resolves
# them at runtime via runtime.GOARCH. Use a bind mount so only the tree matching
# $TARGETARCH ends up in an image layer (the unused arch never materialises).
ARG TARGETARCH
RUN --mount=type=bind,source=assets/tools,target=/ctx/tools,readonly \
mkdir -p /app/assets/tools && \
if [ "$TARGETARCH" = "amd64" ]; then \
cp -r /ctx/tools/x64 /app/assets/tools/x64; \
elif [ "$TARGETARCH" = "arm64" ]; then \
cp -r /ctx/tools/arm /app/assets/tools/arm; \
fi && \
chmod +x /app/assets/tools/*/postgresql/*/bin/* \
/app/assets/tools/*/mysql/*/bin/* \
/app/assets/tools/*/mariadb/*/bin/* \
/app/assets/tools/*/mongodb/bin/*
# Create postgres user and set up directories
RUN groupadd -g 999 postgres || true && \
useradd -m -s /bin/bash -u 999 -g 999 postgres || true && \
mkdir -p /databasus-data/pgdata && \
chown -R postgres:postgres /databasus-data/pgdata
# Create non-root user for the main application process
RUN useradd -r -s /usr/sbin/nologin -u 65532 databasus
WORKDIR /app
# Copy Goose from build stage
COPY --from=backend-build /usr/local/bin/goose /usr/local/bin/goose
# Copy app binary
COPY --from=backend-build /app/main .
# Expose the binary as the `databasus` command on PATH (e.g. `databasus healthcheck`)
RUN ln -s /app/main /usr/local/bin/databasus
# Copy migrations directory
COPY backend/migrations ./migrations
# Copy UI files
COPY --from=backend-build /app/ui/build ./ui/build
# Copy verification agent binaries (both architectures) — served by the backend
# at GET /api/v1/system/verification-agent?arch=amd64|arm64
RUN mkdir -p ./agent-binaries
COPY --from=verification-agent-build /verification-agent-binaries/* ./agent-binaries/
# Bake .env.example as /.env so the binary has defaults when no env file is
# mounted. The backend looks for .env at the parent of cwd (= /app), i.e. /.
# Real env vars (-e, compose, k8s) take precedence — godotenv.Load does not
# overwrite already-set variables.
COPY .env.example /.env
# Create startup script
COPY <<EOF /app/start.sh
#!/bin/bash
set -e
# Check for legacy postgresus-data volume mount
if [ -d "/postgresus-data" ] && [ "\$(ls -A /postgresus-data 2>/dev/null)" ]; then
echo ""
echo "=========================================="
echo "ERROR: Legacy volume detected!"
echo "=========================================="
echo ""
echo "You are using the \`postgresus-data\` folder. It seems you changed the image name from Postgresus to Databasus without changing the volume."
echo ""
echo "Please either:"
echo " 1. Switch back to image rostislavdugin/postgresus:latest (supported until ~Dec 2026)"
echo " 2. Read the migration guide: https://databasus.com/installation/#postgresus-migration"
echo ""
echo "=========================================="
exit 1
fi
# ========= Adjust postgres user UID/GID =========
PUID=\${PUID:-999}
PGID=\${PGID:-999}
CURRENT_UID=\$(id -u postgres)
CURRENT_GID=\$(id -g postgres)
if [ "\$CURRENT_GID" != "\$PGID" ]; then
echo "Adjusting postgres group GID from \$CURRENT_GID to \$PGID..."
groupmod -o -g "\$PGID" postgres
fi
if [ "\$CURRENT_UID" != "\$PUID" ]; then
echo "Adjusting postgres user UID from \$CURRENT_UID to \$PUID..."
usermod -o -u "\$PUID" postgres
fi
# PostgreSQL 17 binary paths
PG_BIN="/usr/lib/postgresql/17/bin"
# Generate runtime configuration for frontend
echo "Generating runtime configuration..."
# Detect if email is configured (both SMTP_HOST and DATABASUS_URL must be set)
if [ -n "\${SMTP_HOST:-}" ] && [ -n "\${DATABASUS_URL:-}" ]; then
IS_EMAIL_CONFIGURED="true"
else
IS_EMAIL_CONFIGURED="false"
fi
cat > /app/ui/build/runtime-config.js <<JSEOF
// Runtime configuration injected at container startup
// This file is generated dynamically and should not be edited manually
window.__RUNTIME_CONFIG__ = {
GITHUB_CLIENT_ID: '\${GITHUB_CLIENT_ID:-}',
GOOGLE_CLIENT_ID: '\${GOOGLE_CLIENT_ID:-}',
IS_EMAIL_CONFIGURED: '\$IS_EMAIL_CONFIGURED',
CLOUDFLARE_TURNSTILE_SITE_KEY: '\${CLOUDFLARE_TURNSTILE_SITE_KEY:-}',
CONTAINER_ARCH: '\${CONTAINER_ARCH:-unknown}'
};
JSEOF
# Inject analytics script if provided (only if not already injected)
if [ -n "\${ANALYTICS_SCRIPT:-}" ]; then
if ! grep -q "rybbit.databasus.com" /app/ui/build/index.html 2>/dev/null; then
echo "Injecting analytics script..."
sed -i "s#</head># \${ANALYTICS_SCRIPT}\\
</head>#" /app/ui/build/index.html
fi
fi
# Ensure proper ownership of data directory
echo "Setting up data directory permissions..."
mkdir -p /databasus-data/pgdata
mkdir -p /databasus-data/temp
mkdir -p /databasus-data/backups
chown databasus:databasus /databasus-data
chown -R postgres:postgres /databasus-data/pgdata
chown -R databasus:databasus /databasus-data/temp /databasus-data/backups
# Upgrade path: secret.key and instance.json may be owned by root or postgres
# from older images. Re-own them so the non-root main process can read/write.
chown databasus:databasus /databasus-data/secret.key /databasus-data/instance.json 2>/dev/null || true
chmod 700 /databasus-data/temp
# ========= Start Valkey (internal cache) =========
echo "Configuring Valkey cache..."
cat > /tmp/valkey.conf << 'VALKEY_CONFIG'
port 6379
bind 127.0.0.1
protected-mode yes
save ""
maxmemory 256mb
maxmemory-policy allkeys-lru
VALKEY_CONFIG
echo "Starting Valkey..."
valkey-server /tmp/valkey.conf &
VALKEY_PID=\$!
echo "Waiting for Valkey to be ready..."
for i in {1..30}; do
if valkey-cli ping >/dev/null 2>&1; then
echo "Valkey is ready!"
break
fi
sleep 1
done
# Initialize PostgreSQL if not already initialized
if [ ! -s "/databasus-data/pgdata/PG_VERSION" ]; then
echo "Initializing PostgreSQL database..."
gosu postgres \$PG_BIN/initdb -D /databasus-data/pgdata --encoding=UTF8 --locale=C.UTF-8
# Configure PostgreSQL
echo "host all all 127.0.0.1/32 md5" >> /databasus-data/pgdata/pg_hba.conf
echo "local all all trust" >> /databasus-data/pgdata/pg_hba.conf
echo "port = 5437" >> /databasus-data/pgdata/postgresql.conf
echo "listen_addresses = 'localhost'" >> /databasus-data/pgdata/postgresql.conf
echo "shared_buffers = 256MB" >> /databasus-data/pgdata/postgresql.conf
echo "max_connections = 100" >> /databasus-data/pgdata/postgresql.conf
fi
# Function to start PostgreSQL and wait for it to be ready
start_postgres() {
echo "Starting PostgreSQL..."
# -k /tmp: create Unix socket and lock file in /tmp instead of /var/run/postgresql/.
# On NAS systems (e.g. TrueNAS Scale), the ZFS-backed Docker overlay filesystem
# ignores chown/chmod on directories from image layers, so PostgreSQL gets
# "Permission denied" when creating .s.PGSQL.5437.lock in /var/run/postgresql/.
# All internal connections use TCP (-h localhost), so the socket location does not matter.
gosu postgres \$PG_BIN/postgres -D /databasus-data/pgdata -p 5437 -k /tmp &
POSTGRES_PID=\$!
echo "Waiting for PostgreSQL to be ready..."
for i in {1..30}; do
if gosu postgres \$PG_BIN/pg_isready -p 5437 -h localhost >/dev/null 2>&1; then
echo "PostgreSQL is ready!"
return 0
fi
sleep 1
done
return 1
}
# Try to start PostgreSQL
if ! start_postgres; then
echo ""
echo "=========================================="
echo "PostgreSQL failed to start. Attempting WAL reset recovery..."
echo "=========================================="
echo ""
# Kill any remaining postgres processes
pkill -9 postgres 2>/dev/null || true
sleep 2
# Attempt pg_resetwal to recover from WAL corruption
echo "Running pg_resetwal to reset WAL..."
if gosu postgres \$PG_BIN/pg_resetwal -f /databasus-data/pgdata; then
echo "WAL reset successful. Restarting PostgreSQL..."
# Try starting PostgreSQL again after WAL reset
if start_postgres; then
echo "PostgreSQL recovered successfully after WAL reset!"
else
echo ""
echo "=========================================="
echo "ERROR: PostgreSQL failed to start even after WAL reset."
echo "The database may be severely corrupted."
echo ""
echo "Options:"
echo " 1. Delete the volume and start fresh (data loss)"
echo " 2. Manually inspect /databasus-data/pgdata for issues"
echo "=========================================="
exit 1
fi
else
echo ""
echo "=========================================="
echo "ERROR: pg_resetwal failed."
echo "The database may be severely corrupted."
echo ""
echo "Options:"
echo " 1. Delete the volume and start fresh (data loss)"
echo " 2. Manually inspect /databasus-data/pgdata for issues"
echo "=========================================="
exit 1
fi
fi
# Create database and set password for postgres user
echo "Setting up database and user..."
gosu postgres \$PG_BIN/psql -p 5437 -h localhost -d postgres << 'SQL'
-- We use stub password, because internal DB is not exposed outside container
ALTER USER postgres WITH PASSWORD 'Q1234567';
SELECT 'CREATE DATABASE databasus OWNER postgres'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'databasus')
\\gexec
\\q
SQL
# ========= Refuse to start if legacy WAL backup data exists =========
# The agent-based WAL_V1 backup type was removed. Existing installs with
# WAL-mode databases must downgrade, manually remove them and then upgrade.
echo "Checking for legacy WAL backup configuration..."
WAL_CHECK_DSN="postgres://postgres:Q1234567@localhost:5437/databasus"
WAL_CHECK_COL=\$(gosu databasus \$PG_BIN/psql "\$WAL_CHECK_DSN" -tA -c "SELECT 1 FROM information_schema.columns WHERE table_name='postgresql_databases' AND column_name='backup_type' LIMIT 1" 2>/dev/null || true)
if [ "\$WAL_CHECK_COL" = "1" ]; then
WAL_CHECK_ROW=\$(gosu databasus \$PG_BIN/psql "\$WAL_CHECK_DSN" -tA -c "SELECT 1 FROM postgresql_databases WHERE backup_type='WAL_V1' LIMIT 1" 2>/dev/null || true)
if [ "\$WAL_CHECK_ROW" = "1" ]; then
echo ""
echo "=========================================="
echo "ERROR: Agent (WAL_V1) backup approach is no longer supported."
echo "=========================================="
echo ""
echo "Please downgrade to version 3.42.0, remove all WAL-mode databases"
echo "manually and then upgrade again. This safeguard exists to avoid"
echo "corrupting already-set-up agents."
echo ""
echo "=========================================="
exit 1
fi
fi
echo "No legacy WAL backup data detected."
# Start the main application
echo "Starting Databasus application..."
exec gosu databasus ./main
EOF
LABEL org.opencontainers.image.source="https://github.com/databasus/databasus"
RUN chmod +x /app/start.sh
EXPOSE 4005
# Liveness probe: the runtime image ships no wget/curl, so the binary checks
# itself. Targets the dependency-free /system/version endpoint (not the deep
# /system/health) so a degraded-but-serving instance is never restarted.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD ["databasus", "healthcheck"]
# Volume for PostgreSQL data
VOLUME ["/databasus-data"]
ENTRYPOINT ["/app/start.sh"]
CMD []