Containerizing a C# hello world app
I've always wanted to have my own little Kubernetes cluster somewhere and I finally decided to create one with Civo. But I had no idea what to run on it. So, this little post is about how I went about creating a hello-world application to run on my cluster!
If you only want to see the code, here you go.
The application itself is a simple C# file inspired by Jess Frazelle's much cooler party-clippy:
const string CLIPPY = @"
__
/ \ _____________
| | / \
@ @ | It looks |
|| || | like you |
|| || <--| are very |
|\_/| | bored. |
\___/ \_____________/
";
var app = WebApplication.CreateBuilder(args).Build();
app.MapFallback(() => CLIPPY);
app.Run();
Next, I put together a Dockerfile
. After some googling around, I decided to use the ubuntu chiseled docker image ubuntu/dotnet-deps
. I've only ever used alpine or debian for C# applications, so this was a first!
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env
WORKDIR /app
COPY ./src ./src
RUN dotnet publish -c Release -r linux-x64 --self-contained -o /app/out ./src/clippy.csproj
FROM ubuntu/dotnet-deps:7.0_edge AS final
LABEL org.opencontainers.image.source="https://github.com/gldraphael/clippy"
LABEL org.opencontainers.image.description="A simple hello world application."
ENV \
# Configure web servers to bind to port 80 when present
ASPNETCORE_URLS=http://+:80 \
# Enable detection of running in a container
DOTNET_RUNNING_IN_CONTAINER=true \
# Disable globalization
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 80
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["./clippy"]
At this point, I had something I could run in a container.
Now that I had a working docker image, I wanted to be able to publish this to GitHub packages using GitHub actions. This bit was actually the easiest. I literally just pasted this example from the documentation, and it just worked!
# pull the latest image
docker pull ghcr.io/gldraphael/clippy:main
# run it in the background
docker run -it -d -p 8080:80 --name clippy --rm ghcr.io/gldraphael/clippy:main
# curl it
curl localhost:8080
# once you're done, stop it to delete it
docker stop clippy
This also happens to be the first time I've ever used GitHub packages. Another first. Yay!
Next, I wanted a helm chart. Creating the chart itself was easy. helm create chart
did it. I only had to go in and update a few things (like change the app name from chart
to clippy
, update the image to use, and so on).
Trouble was, I wasn't even sure if GitHub container registry supported OCI artifacts yet. Lucky for me, Niklas Metje documented this on his blog. I just had to figure out the workflow file. I ended up using Stefan Prodan's podinfo project as an example and decided to go with something like this:
name: Push helm chart
on:
push:
branches: ['main']
env:
REGISTRY: ghcr.io
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Helm
uses: azure/setup-helm@v3
with:
version: v3.11.3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish Helm chart to GHCR
run: |
helm package chart
export CHART_VERSION=$(grep 'version:' ./chart/Chart.yaml | tail -n1 | awk '{ print $2 }')
helm push clippy-$CHART_VERSION.tgz oci://${{ env.REGISTRY }}/gldraphael/charts
rm clippy-$CHART_VERSION.tgz
The action seems to push a chart ok, but I'm yet to actually run this on a cluster. Next weekend!