Containerize Bifrost deployment
This commit is contained in:
parent
43a5d1297b
commit
f1823c2a59
7 changed files with 186 additions and 11 deletions
22
.dockerignore
Normal file
22
.dockerignore
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.git
|
||||
.github
|
||||
.agents
|
||||
.bifrost
|
||||
.voidbot
|
||||
bin
|
||||
obj
|
||||
**/bin
|
||||
**/obj
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/TestResults
|
||||
**/*.user
|
||||
**/*.suo
|
||||
.env
|
||||
.env.*
|
||||
!.env.local.example
|
||||
README.md
|
||||
docs
|
||||
plugins
|
||||
tools
|
||||
4
.env.local.example
Normal file
4
.env.local.example
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Optional local overrides for Docker Compose.
|
||||
# The default compose.local.yaml is self-contained for local smoke testing.
|
||||
|
||||
COMPOSE_PROJECT_NAME=bifrost-local
|
||||
54
.github/workflows/publish-container.yml
vendored
Normal file
54
.github/workflows/publish-container.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
name: Publish Container
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "v*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: gamecult/bifrost
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Build and publish Bifrost image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY src/Bifrost.Web/Bifrost.Web.csproj src/Bifrost.Web/
|
||||
RUN dotnet restore src/Bifrost.Web/Bifrost.Web.csproj
|
||||
|
||||
COPY . .
|
||||
RUN dotnet publish src/Bifrost.Web/Bifrost.Web.csproj \
|
||||
--configuration Release \
|
||||
--output /app/publish \
|
||||
/p:UseAppHost=false
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS runtime
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
ENV ASPNETCORE_URLS=http://0.0.0.0:5080 \
|
||||
ASPNETCORE_FORWARDEDHEADERS_ENABLED=true \
|
||||
DOTNET_RUNNING_IN_CONTAINER=true
|
||||
|
||||
EXPOSE 5080
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD curl -fsS http://127.0.0.1:5080/healthz || exit 1
|
||||
|
||||
ENTRYPOINT ["dotnet", "Bifrost.Web.dll"]
|
||||
|
|
@ -15,6 +15,7 @@ This repository now includes a viable alpha foundation slice of the ASP.NET Core
|
|||
- health and readiness endpoints plus startup config validation
|
||||
- GitHub App webhook ingestion for issue, pull request, and review sync
|
||||
- local bridge tooling for agent-owned GitHub draft PRs and Discord posts
|
||||
- Docker image and local Compose stack for containerized smoke testing
|
||||
- xUnit integration tests under `tests/Bifrost.Web.Tests`
|
||||
- matching deploy artifacts and runbooks in `E:\Projects\gamecult-ops`
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ It is closer to deployable now, but it is still not a finished internet-facing m
|
|||
|
||||
- public app hostname: `bifrost.gamecult.org`
|
||||
- infrastructure context: `Yggdrasil`
|
||||
- deployment model: nginx + systemd + localhost Postgres, following the existing GameCult ops pattern
|
||||
- deployment model: GHCR image + Docker Compose behind nginx, with the older systemd path kept as rollback until the container path is boring
|
||||
|
||||
## Next Build Target
|
||||
|
||||
|
|
@ -62,6 +63,8 @@ For the full staged roadmap, including contribution points, revenue share, patro
|
|||
- default connection string points at local PostgreSQL
|
||||
- build with `dotnet build Bifrost.slnx`
|
||||
- test with `DOTNET_ROLL_FORWARD=Major dotnet test Bifrost.slnx` if the machine only has the .NET 10 runtime installed
|
||||
- container smoke test with `docker compose -f compose.local.yaml up --build`
|
||||
- container health checks live at `http://127.0.0.1:5080/healthz` and `http://127.0.0.1:5080/readyz`
|
||||
|
||||
## Read First
|
||||
|
||||
|
|
|
|||
50
compose.local.yaml
Normal file
50
compose.local.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
name: bifrost-local
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
container_name: bifrost-postgres
|
||||
environment:
|
||||
POSTGRES_DB: bifrost_dev
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "127.0.0.1:55432:5432"
|
||||
volumes:
|
||||
- bifrost-postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres -d bifrost_dev"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: ghcr.io/gamecult/bifrost:local
|
||||
container_name: bifrost-web
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: Development
|
||||
ASPNETCORE_URLS: http://0.0.0.0:5080
|
||||
ConnectionStrings__Bifrost: Host=postgres;Database=bifrost_dev;Username=postgres;Password=postgres
|
||||
Bootstrap__ApplyMigrationsOnStartup: "true"
|
||||
GitHubOAuth__ClientId: local-dev-client
|
||||
GitHubOAuth__ClientSecret: local-dev-secret
|
||||
GitHubApp__EnableWebhookSync: "false"
|
||||
Host__PublicBaseUrl: http://localhost:5080
|
||||
Host__ExpectedHost: localhost
|
||||
Host__RequireStrictHostValidation: "false"
|
||||
DataProtection__KeysPath: /var/lib/bifrost/data-protection-keys
|
||||
AllowedHosts: localhost;127.0.0.1
|
||||
ports:
|
||||
- "127.0.0.1:5080:5080"
|
||||
volumes:
|
||||
- bifrost-data-protection:/var/lib/bifrost/data-protection-keys
|
||||
|
||||
volumes:
|
||||
bifrost-postgres:
|
||||
bifrost-data-protection:
|
||||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authentication;
|
|||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.HttpLogging;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
|
@ -67,23 +68,34 @@ builder.Services.AddScoped<ApplicationBootstrapper>();
|
|||
builder.Services.AddSingleton<StartupConfigurationValidator>();
|
||||
builder.Services.AddScoped<IAuthorizationHandler, ActiveMemberAuthorizationHandler>();
|
||||
|
||||
var dataProtectionKeysPath = builder.Configuration["DataProtection:KeysPath"];
|
||||
if (!string.IsNullOrWhiteSpace(dataProtectionKeysPath))
|
||||
{
|
||||
builder.Services
|
||||
.AddDataProtection()
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeysPath));
|
||||
}
|
||||
|
||||
var configuredGitHubOAuthOptions = builder.Configuration
|
||||
.GetSection(GitHubOAuthOptions.SectionName)
|
||||
.Get<GitHubOAuthOptions>() ?? new GitHubOAuthOptions();
|
||||
|
||||
var authenticationBuilder = builder.Services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.LoginPath = "/auth/sign-in";
|
||||
options.AccessDeniedPath = "/Membership/Status";
|
||||
options.SlidingExpiration = true;
|
||||
})
|
||||
.AddOAuth(GitHubAuthenticationDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
var gitHubOptions = builder.Configuration
|
||||
.GetSection(GitHubOAuthOptions.SectionName)
|
||||
.Get<GitHubOAuthOptions>() ?? new GitHubOAuthOptions();
|
||||
});
|
||||
|
||||
options.ClientId = gitHubOptions.ClientId;
|
||||
options.ClientSecret = gitHubOptions.ClientSecret;
|
||||
options.CallbackPath = gitHubOptions.CallbackPath;
|
||||
if (configuredGitHubOAuthOptions.IsConfigured)
|
||||
{
|
||||
authenticationBuilder.AddOAuth(GitHubAuthenticationDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.ClientId = configuredGitHubOAuthOptions.ClientId;
|
||||
options.ClientSecret = configuredGitHubOAuthOptions.ClientSecret;
|
||||
options.CallbackPath = configuredGitHubOAuthOptions.CallbackPath;
|
||||
options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
|
||||
options.TokenEndpoint = "https://github.com/login/oauth/access_token";
|
||||
options.UserInformationEndpoint = "https://api.github.com/user";
|
||||
|
|
@ -118,6 +130,7 @@ builder.Services
|
|||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue