Public Access
It moved to DodoTech-Public, and every address in the product still said DodoTech. That looked like it worked, which is the part worth writing down: Gitea leaves a 301 at the old path and HttpClient follows a redirect on a GET, so both update channels would have kept polling through it. What a 301 does not survive is a POST. `vpk upload gitea` publishes the desktop release by POSTing to that URL, so the stale address would have failed at the one step the whole feature depends on — and a redirect is a thing an operator can delete, which turns "works today" into the same silent outage this session has already spent two commits on. So both channel constants, both release scripts, the workflow's REPO, the image's source label and the curl in phase 16 all name the live path. The local remote too, which had been printing a redirect warning on every push. Measured after the move: the org, the repo and the nightly release all answer 200 anonymously, and that release now carries both assets — the manifest and a 54 MB APK. The upload going through also answers the open question about the reverse proxy's body-size limit, which nothing local could test.
137 lines
7.7 KiB
Docker
137 lines
7.7 KiB
Docker
# The API image. Built from the repository root, not from this directory:
|
|
#
|
|
# docker build -f src/DodoSSH.Api/Dockerfile .
|
|
#
|
|
# Directory.Build.props, Directory.Packages.props, NuGet.config and global.json all sit at
|
|
# the root and all four are load-bearing here — central package management means a csproj
|
|
# alone does not name a single version, and a build that cannot see them resolves nothing.
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# Build
|
|
# ---------------------------------------------------------------------------------------
|
|
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS build
|
|
|
|
# Reproducible builds. Directory.Build.props turns ContinuousIntegrationBuild on when this
|
|
# is set, which is what normalises the source paths baked into the PDBs — without it two
|
|
# builds of the same commit differ by the directory they happened in.
|
|
ENV CI=true \
|
|
DOTNET_NOLOGO=true \
|
|
DOTNET_CLI_TELEMETRY_OPTOUT=true \
|
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true
|
|
|
|
WORKDIR /src
|
|
|
|
# The manifests first, and only the manifests. This layer is what makes an ordinary code
|
|
# change a ten-second rebuild instead of a full package restore: it is invalidated by a
|
|
# dependency change and by nothing else. Every project in DodoSSH.Api's reference closure
|
|
# has to be here — restore walks ProjectReference, and a missing csproj fails the graph
|
|
# rather than skipping a node.
|
|
COPY global.json NuGet.config Directory.Build.props Directory.Packages.props ./
|
|
COPY src/DodoSSH.Api/DodoSSH.Api.csproj src/DodoSSH.Api/
|
|
COPY src/DodoSSH.Api/packages.lock.json src/DodoSSH.Api/
|
|
COPY src/DodoSSH.Contracts/DodoSSH.Contracts.csproj src/DodoSSH.Contracts/
|
|
COPY src/DodoSSH.Contracts/packages.lock.json src/DodoSSH.Contracts/
|
|
COPY src/DodoSSH.Crypto/DodoSSH.Crypto.csproj src/DodoSSH.Crypto/
|
|
COPY src/DodoSSH.Crypto/packages.lock.json src/DodoSSH.Crypto/
|
|
COPY src/DodoSSH.Domain/DodoSSH.Domain.csproj src/DodoSSH.Domain/
|
|
COPY src/DodoSSH.Domain/packages.lock.json src/DodoSSH.Domain/
|
|
COPY src/DodoSSH.Infrastructure/DodoSSH.Infrastructure.csproj src/DodoSSH.Infrastructure/
|
|
COPY src/DodoSSH.Infrastructure/packages.lock.json src/DodoSSH.Infrastructure/
|
|
|
|
# Locked mode here for the same reason CI uses it: the lock files are committed, so a
|
|
# dependency that changed without its lock file being reviewed fails the build rather than
|
|
# quietly shipping. An image is the one place that matters most.
|
|
RUN dotnet restore src/DodoSSH.Api/DodoSSH.Api.csproj --locked-mode
|
|
|
|
# BannedSymbols.txt is an AdditionalFiles entry in Directory.Build.props. Without it the
|
|
# BannedApiAnalyzers rules silently pass, and with TreatWarningsAsErrors the whole point of
|
|
# the list is that it fails a build — so its absence would be invisible in exactly the way
|
|
# it is meant to prevent.
|
|
COPY BannedSymbols.txt .editorconfig ./
|
|
COPY src/ src/
|
|
|
|
# The version, handed in rather than derived, because there is no repository in here to derive
|
|
# it from: MinVer reads git tags, and .dockerignore excludes .git/ deliberately — the context is
|
|
# the repository root and copying the whole history into every image build would be absurd.
|
|
#
|
|
# Without this the build still succeeds (MINVER1001 is a warning, and TreatWarningsAsErrors does
|
|
# not escalate a task warning), and that is the trap: the image would be built with the SDK's
|
|
# fallback version and GET /api/v1/meta would report 0.0.0-alpha.0 as its serverVersion, which is
|
|
# a lie told quietly. The tag is already parsed by the workflow for the image tags, so it is the
|
|
# same number, passed one step further.
|
|
#
|
|
# MinVerSkip because there is nothing here for it to do, and it should not warn about it either.
|
|
#
|
|
# ASSEMBLY_VERSION and emphatically not VERSION, which is the trap this block exists to avoid and
|
|
# which cost a build to find. An ARG is an environment variable for the rest of the stage, MSBuild
|
|
# reads environment variables as global properties, and property names are case-insensitive — so an
|
|
# `ARG VERSION` in a build stage silently sets MSBuild's `Version` for every project in it. With the
|
|
# workflow passing `main-<short sha>` on a main build, that is not a version the SDK will accept, and
|
|
# the publish dies with NETSDK1018 "Invalid NuGet version string" pointing at DodoSSH.Contracts, a
|
|
# project nobody changed. The name is the whole fix; the ARG in the final stage below is only ever a
|
|
# label and never meets MSBuild.
|
|
#
|
|
# The workflow passes this empty except on a tag build, so a main image keeps the SDK default rather
|
|
# than carrying a version that is not one.
|
|
ARG ASSEMBLY_VERSION=""
|
|
|
|
RUN dotnet publish src/DodoSSH.Api/DodoSSH.Api.csproj \
|
|
--no-restore \
|
|
--configuration Release \
|
|
--output /app \
|
|
-p:MinVerSkip=true \
|
|
${ASSEMBLY_VERSION:+-p:Version="$ASSEMBLY_VERSION"} \
|
|
-p:UseAppHost=false
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
# Runtime
|
|
# ---------------------------------------------------------------------------------------
|
|
#
|
|
# Chiseled: no shell, no package manager, no libc utilities, and a non-root user (uid 1654)
|
|
# already set by the base image. That closes off `docker exec sh` on a process that holds a
|
|
# database connection and the cursor signing key, and it is affordable here specifically
|
|
# because Directory.Build.props sets InvariantGlobalization — the ICU and tzdata a normal
|
|
# base carries are exactly what this product has already decided it does not use.
|
|
#
|
|
# The cost is real and worth stating: there is no HEALTHCHECK below, because there is no
|
|
# curl and nothing to run one with. The health endpoints exist and are anonymous —
|
|
# /healthz/live, /healthz/ready, /healthz/startup — so the probe belongs in whatever runs
|
|
# the container. Readiness is not decoration on this API: it fails while an EF migration is
|
|
# pending and names the one it is waiting for, which is the intended way to discover that a
|
|
# deployment shipped ahead of its schema.
|
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS final
|
|
|
|
# Passed by CI; see .github/workflows/ci.yml. Declared with empty defaults so a local
|
|
# `docker build` with no arguments still succeeds.
|
|
ARG VERSION=""
|
|
ARG REVISION=""
|
|
ARG CREATED=""
|
|
|
|
LABEL org.opencontainers.image.title="DodoSSH API" \
|
|
org.opencontainers.image.description="DodoSSH server: sync, identity, teams and relay authorization." \
|
|
org.opencontainers.image.vendor="DodoTech" \
|
|
org.opencontainers.image.licenses="MIT" \
|
|
org.opencontainers.image.source="https://git.dodotech.cloud/DodoTech-Public/DodoSSH" \
|
|
org.opencontainers.image.version="${VERSION}" \
|
|
org.opencontainers.image.revision="${REVISION}" \
|
|
org.opencontainers.image.created="${CREATED}"
|
|
|
|
WORKDIR /app
|
|
COPY --from=build /app .
|
|
|
|
# 8080 is the .NET container default (ASPNETCORE_HTTP_PORTS in the base image), and plain
|
|
# HTTP is deliberate: Program.cs has no UseHttpsRedirection because the API is always behind
|
|
# a proxy that terminates TLS, and redirecting from here would loop.
|
|
EXPOSE 8080
|
|
|
|
# Configuration reaches the process two ways, both already wired in Program.cs: environment
|
|
# variables prefixed DODOSSH_, and files under /run/secrets for anything that should not be
|
|
# readable in `docker inspect`. The three the process will not start or run correctly
|
|
# without are DODOSSH_ConnectionStrings__Postgres, DODOSSH_Oidc__Authority and — on more
|
|
# than one node — DODOSSH_Sync__CursorSigningKey.
|
|
#
|
|
# Nothing migrates the database from in here. That is the API's own design: it fails
|
|
# /healthz/ready while a migration is pending and names it, so the schema is applied by
|
|
# `dotnet ef database update` alongside the deployment rather than by a racing container.
|
|
ENTRYPOINT ["dotnet", "DodoSSH.Api.dll"]
|