Self-hosted Gitea on a single e2-small AlmaLinux 10 VM in us-east1, serving gitea.jasonmross.dev. Runtime is podman quadlets (systemd .container/.network/.volume units). Both images are built on Debian 13: Gitea from a GPG-verified release binary, and Caddy from an xcaddy build carrying the Google Cloud DNS provider (ACME DNS-01) and the Coraza WAF with the OWASP CRS embedded. Infrastructure is a Pulumi program in Go against a GCS state backend. Cloud Build handles CI: a push trigger for images, one for infra, and a weekly scheduled rebuild. Everything Cloud Build touches is 2nd gen. Notable design decisions, each documented where it lives: - Quadlets track a floating :prod tag. AutoUpdate=registry compares digests for a tag, so a digest-pinned image silently disables auto-updates. - Git transport and LFS bypass the WAF. With the bypass removed, a plain git push returns 403 -- packfiles trip CRS reliably. - gitea:wafMode drives both SecRuleEngine and whether the fail2ban jail acting on WAF verdicts exists. Banning on detections that were never blocks would turn a tuning false positive into an nftables ban. - fail2ban bans at the nftables prerouting hook. Published container ports are DNAT'd and never traverse INPUT, where the stock actions install their rules. - The DNS zone, backup bucket, and Gitea signing secrets are not Pulumi-owned, so pulumi destroy cannot take them with it. - The podman subnet is pinned because it is what Gitea's REVERSE_PROXY_TRUSTED_PROXIES names. Three update layers: dnf5-automatic for the OS, podman-auto-update with health-gated rollback for containers, and a weekly image rebuild that gives the second layer something to pull. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
40 lines
1.4 KiB
Go
40 lines
1.4 KiB
Go
// Package registry holds the Artifact Registry repository the VM pulls from.
|
|
package registry
|
|
|
|
import (
|
|
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/artifactregistry"
|
|
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
|
|
|
|
"gitea-infra/pkg/config"
|
|
)
|
|
|
|
const RepoID = "gitea"
|
|
|
|
func New(ctx *pulumi.Context, cfg *config.Config, deps []pulumi.Resource) (*artifactregistry.Repository, error) {
|
|
return artifactregistry.NewRepository(ctx, "gitea-repo", &artifactregistry.RepositoryArgs{
|
|
RepositoryId: pulumi.String(RepoID),
|
|
Location: pulumi.String(cfg.Region),
|
|
Format: pulumi.String("DOCKER"),
|
|
Description: pulumi.String("Gitea and Caddy images, Debian 13 base"),
|
|
CleanupPolicies: artifactregistry.RepositoryCleanupPolicyArray{
|
|
// Untagged layers accumulate fast with a weekly rebuild.
|
|
&artifactregistry.RepositoryCleanupPolicyArgs{
|
|
Id: pulumi.String("delete-untagged"),
|
|
Action: pulumi.String("DELETE"),
|
|
Condition: &artifactregistry.RepositoryCleanupPolicyConditionArgs{
|
|
TagState: pulumi.String("UNTAGGED"),
|
|
OlderThan: pulumi.String("604800s"), // 7 days
|
|
},
|
|
},
|
|
// Keep enough tagged versions to roll back a few deploys.
|
|
&artifactregistry.RepositoryCleanupPolicyArgs{
|
|
Id: pulumi.String("keep-recent"),
|
|
Action: pulumi.String("KEEP"),
|
|
MostRecentVersions: &artifactregistry.RepositoryCleanupPolicyMostRecentVersionsArgs{
|
|
KeepCount: pulumi.Int(20),
|
|
},
|
|
},
|
|
},
|
|
}, pulumi.DependsOn(deps))
|
|
}
|