Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,4 @@ jobs:
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
65 changes: 65 additions & 0 deletions .github/workflows/containers.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Create containers

on:
# run every night
schedule:
- cron: "0 22 * * *"

# schedule manually
workflow_dispatch:
inputs:
# On workflow dispatch, `branch` is selected by default
# You can access it in `github.ref_name`

tag_name:
description: "Tag name for the container"
required: true
default: "nightly"

container_repository_branch:
description: "Branch of the container repository"
required: true
default: "main"

jobs:
build-and-push-containers:
name: Build and push container images to Quay
if: github.event_name != 'schedule' || github.repository_owner == 'geo-engine'

runs-on: ubuntu-24.04

permissions:
contents: read

env:
TAG_NAME: nightly
BACKEND_CONTAINER_NAME: biois-backend
FRONTEND_CONTAINER_NAME: biois-frontend

steps:
- name: Modify TAG_NAME if on `tag_name` is set on `workflow_dispatch`
if: github.event.inputs.tag_name != ''
run: |
echo "TAG_NAME=${{ github.event.inputs.tag_name }}" >> $GITHUB_ENV

- name: Checkout code
uses: actions/checkout@v6

- name: Login to quay.io
run: podman login -u="geoengine+bot" -p="${{secrets.QUAY_IO_TOKEN}}" quay.io

- name: Build containers
run: |
just build-backend-container
just build-frontend-container

- name: Push image to quay.io
run: |
podman push ${{env.BACKEND_CONTAINER_NAME}}:${{env.TAG_NAME}} quay.io/geoengine/${{env.BACKEND_CONTAINER_NAME}}:${{env.TAG_NAME}}
podman push ${{env.FRONTEND_CONTAINER_NAME}}:${{env.TAG_NAME}} quay.io/geoengine/${{env.FRONTEND_CONTAINER_NAME}}:${{env.TAG_NAME}}

- name: Push nightly with date
if: env.TAG_NAME == 'nightly'
run: |
podman push ${{env.BACKEND_CONTAINER_NAME}}:${{env.TAG_NAME}} quay.io/geoengine/${{env.BACKEND_CONTAINER_NAME}}:${{env.TAG_NAME}}-$(date +'%Y-%m-%d')
podman push ${{env.FRONTEND_CONTAINER_NAME}}:${{env.TAG_NAME}} quay.io/geoengine/${{env.FRONTEND_CONTAINER_NAME}}:${{env.TAG_NAME}}-$(date +'%Y-%m-%d')
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ The API client is a TypeScript library generated from the OpenAPI specification

### [Frontend](frontend/README.md)

_TODO: Add description of frontend component._
The frontend is an Angular application that provides a user interface for interacting with the BioIS service.
11 changes: 11 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
target
.git
.gitignore
node_modules
dist
*.log
*.env
/.venv
# Keep Cargo.lock in context for reproducible builds
# Cargo.lock
**/target
39 changes: 39 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Backend multi-stage build:
# 1. build with Rust
# 2. copy release binary into distroless final image

FROM quay.io/geoengine/devcontainer:latest AS builder

WORKDIR /usr/src/biois

# Cache dependencies: copy manifest first
COPY \
Cargo.toml \
Cargo.lock \
rust-toolchain.toml \
./
# create dummy src to allow caching of cargo registry build step
RUN mkdir src && printf "fn main() { println!(\"cargo build cache\"); }\n" > src/main.rs
RUN cargo build --release
RUN rm -rf src

# Copy full source and build the release binary
COPY conf ./conf
COPY migrations ./migrations
COPY src ./src
COPY build.rs ./build.rs
RUN cargo build --release --bin biois

# Strip the binary to reduce size if possible
RUN strip target/release/biois || true

# Final image: distroless for minimal attack surface and size
FROM gcr.io/distroless/cc-debian13:nonroot

# Copy ca-certificates and the built binary
COPY --from=builder /usr/src/biois/target/release/biois /usr/local/bin/biois
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

USER nonroot
EXPOSE 4040
ENTRYPOINT ["/usr/local/bin/biois"]
12 changes: 12 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
dist
.git
.gitignore
npm-debug.log
yarn-error.log
.cache
/.idea
/.vscode
coverage
docs
*.local
34 changes: 34 additions & 0 deletions frontend/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
# Disable Caddy admin api
admin off
}

:80

# Enable Compression
encode zstd gzip

# Proxy API requests to the backend
handle /api* {
uri strip_prefix /api

reverse_proxy biois-backend:4040
}

# Match static assets
@static {
file
path *.js *.css *.png *.jpg *.jpeg *.gif *.svg *.ico *.woff *.woff2
}

# Set Cache-Control to 1 day (86400 seconds)
header @static Cache-Control "public, max-age=86400"

# ALWAYS keep index.html fresh
header /index.html Cache-Control "no-cache, no-store, must-revalidate"

handle {
root * /usr/share/caddy
try_files {path} /index.html
file_server
}
38 changes: 38 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Frontend multi-stage build:
# 1. build with devcontainer
# 2. serve static with Caddy

FROM quay.io/geoengine/devcontainer:latest AS builder

# 1.1 Install dependencies

WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci --silent

WORKDIR /app/api-client/typescript
COPY api-client/typescript .
RUN npm ci --silent

# 1.2 Copy source and build
WORKDIR /app/frontend
COPY frontend/public ./public
COPY frontend/src ./src
COPY \
frontend/_theme-colors.scss \
frontend/angular.json \
frontend/tsconfig*.json \
./
RUN npm run build --silent

# 2. Final image: Caddy to serve static files and reverse-proxy API
FROM caddy:2-alpine AS runner

# Copy built site into caddy's web root
COPY --from=builder /app/frontend/dist/BioIS/browser /usr/share/caddy

# Copy Caddyfile for SPA fallback and API proxy
COPY frontend/Caddyfile /etc/caddy/Caddyfile

EXPOSE 80
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
3 changes: 3 additions & 0 deletions frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
},
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"proxyConfig": "proxy.conf.json"
},
"configurations": {
"production": {
"buildTarget": "BioIS:build:production"
Expand Down
9 changes: 9 additions & 0 deletions frontend/proxy.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"/api": {
"target": "http://localhost:4040",
"secure": false,
"changeOrigin": true,
"logLevel": "info",
"pathRewrite": { "^/api": "" }
}
}
4 changes: 2 additions & 2 deletions frontend/src/app/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class UserService {
accessToken: 'Bearer ' + this.user()?.id /* TODO: handle missing/expired token */,
};
const config = createConfiguration({
baseServer: new ServerConfiguration('http://localhost:4040', {}),
baseServer: new ServerConfiguration('/api', {}),
// authMethods: authMethods as AuthMethodsConfiguration,
authMethods: {
default: {
Expand Down Expand Up @@ -105,7 +105,7 @@ function oidcRedirectUri(): string {

function configuration(/*options: { authMethods?: OAuth2Configuration } = {}*/): Configuration {
return createConfiguration({
baseServer: new ServerConfiguration('http://localhost:4040', {}),
baseServer: new ServerConfiguration('/api', {}),
// ...options,
});
}
Expand Down
57 changes: 57 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,26 @@ build-frontend:
npm ci
npm run build

# Build the frontend container. Usage: `just build-frontend-container`.
[group('build')]
[group('container')]
build-frontend-container:
@-clear
podman build \
-f frontend/Dockerfile \
-t biois-frontend:latest \
.

# Build the frontend container. Usage: `just build-frontend-container`.
[group('build')]
[group('container')]
build-backend-container:
@-clear
podman build \
-f backend/Dockerfile \
-t biois-backend:latest \
backend

### LINT ###

[group('api-client')]
Expand Down Expand Up @@ -204,6 +224,43 @@ run-frontend:
@-clear
npm run ng serve

# Run the backend container in dev mode. Usage: `just run-backend-container`.
[group('container')]
[group('run')]
run-backend-container: build-backend-container
podman run --rm --replace \
--name biois-backend-dev \
--network host \
-p 4040:4040 \
-v $(pwd)/backend/Settings.toml:/usr/local/bin/Settings.toml \
biois-backend:latest

# Run the frontend container in dev mode. Usage: `just run-frontend-container`.
[group('container')]
[group('run')]
run-frontend-container: build-frontend-container
podman run --rm --replace \
--name biois-frontend-dev \
-p 4200:80 \
biois-frontend:latest

# Run the container as a pod in dev mode. Usage: `just run-pod`.
[group('container')]
[group('run')]
run-pod: build-backend-container build-frontend-container
cat k8s/dev-config.yaml k8s/pod.yaml | \
podman play kube \
--network=pasta:-T,3030:3030 `# Map local Geo Engine at port 3030 into pod` \
--replace -

# Stop the pod in dev mode. Usage: `just down-pod`.
[group('container')]
[group('run')]
down-pod:
cat k8s/dev-config.yaml k8s/pod.yaml | \
podman play kube \
--down -

### MISC ###

# Generate the OpenAPI spec and write it to `openapi.json`.
Expand Down
59 changes: 59 additions & 0 deletions k8s/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Containers

This directory contains Kubernetes manifests for running the BioIS backend and frontend in containers.
The manifests are designed for development and testing purposes, using a single Pod to host both services and a PVC for PostgreSQL data persistence.

## Running with Podman

Build images with Podman (from repository root):

```bash
just build-backend-container
just build-frontend-container
```

You can run them individually with `podman run`:

```bash
just run-frontend-container
just run-backend-container
```

## Running with Podman and Kubernetes manifests

Run locally using `podman play kube` with the provided manifest. The manifest now contains a single Pod and a PVC; Podman does not support Service objects.

Before applying, create a named Podman volume and use its mountpoint as the hostPath backing store for Postgres (no PersistentVolume resource is required):

```bash
# optionally, create a named podman volume
podman volume create biois-postgres

# build containers and run pods
just run-pod
```

Inspect running containers and ports:

```bash
podman ps
podman port <container-id-or-name>
```

Stop the deployed resources:

```bash
just down-pod
```

## Podman: Persistent volumes & Secrets

- The development `ConfigMap` and `Secret` are in `k8s/dev-config.yaml`.
It contains the `biois-config` ConfigMap and `biois-postgres-secret` Secret used by the Pod.
To override credentials, update that file or create a different Secret and apply it before the Pod.

Example: create an overriding secret and then apply manifests

```bash
kubectl create secret generic biois-postgres-secret --from-literal=POSTGRES_USER=youruser --from-literal=POSTGRES_PASSWORD=yourpass --from-literal=POSTGRES_DB=yourdb
```
Loading
Loading