Guests snap photos through the app during a wedding or party, and everything pools into one shared roll that develops and is revealed at the end of the night.
.NET 10 / ASP.NET Core · Azure · Lynx (mobile) · Stripe · Terraform · Docker
| Folder | What | Tech |
|---|---|---|
api/ |
REST API, events, photos, payments | .NET 10 / ASP.NET Core + Stripe |
app/ |
Native mobile app | Lynx via vue-lynx + @vyui |
infra/ |
Azure infra as code | Terraform (Container Apps, ACR, Blob Storage) |
Photo blobs live in Azure Blob Storage, and metadata lives in the API's
database. Payments are optional; set Stripe__SecretKey to enable paid
upgrades.
The API needs a Postgres instance. Spin one up with Docker, then run the API:
docker run -d --name picknic-db -p 5432:5432 \
-e POSTGRES_DB=picknic -e POSTGRES_USER=picknic -e POSTGRES_PASSWORD=picknic \
postgres:17
cd api
dotnet runThe connection string lives in appsettings.Development.json (ConnectionStrings:Default); override it with ConnectionStrings__Default in production.
cd app
pnpm install
pnpm dev # scan with the Lynx Explorer appIn development the app targets a local API on port 8080. When opened from a
device QR, it reuses the dev bundle host so the device calls your machine's LAN
IP instead of its own localhost. To force a specific API host, create
app/.env.local with PUBLIC_API_BASE=http://<host>:8080.
docker compose up --build # api on :8080The compose stack includes Azurite (Azure Blob emulator) on :10000, so the
full photo upload → gallery flow works locally with no Azure account. The API
signs account-key SAS against Azurite in dev and switches to managed-identity
user-delegation SAS in production automatically. Event Grid isn't emulated —
locally, photos register via the /uploads/complete client callback instead.
If the API logs Access to the path '/home/app/.aspnet/DataProtection-Keys/...' is denied, rebuild the compose stack so the dpkeys-init helper can repair the
named volume permissions. If the volume was created by an older stack and still
fails, remove it once with docker volume rm picknic_dpkeys, then rebuild.
Payments are optional. Without a key the /api/checkout endpoint returns 501
and the app runs fine. To enable them:
export STRIPE_SECRET_KEY=sk_test_... # docker compose picks this up
# or set Stripe:SecretKey in api/appsettings.Development.jsoncd infra/terraform
cp terraform.tfvars.example terraform.tfvars # fill in values
terraform init
terraform applyThis provisions a resource group, Container Registry, Blob Storage, a
PostgreSQL flexible server, Log Analytics, and a Container App running the API.
Set postgres_admin_password in terraform.tfvars; the connection string is
injected into the Container App as ConnectionStrings__Default.
Picknic is licensed under AGPL-3.0. The code is open, but running a hosted or closed competing service requires open-sourcing your changes. The official Picknic service and name are operated by the maintainer.