<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[the (mostly) friendly Opministrator]]></title><description><![CDATA[the (mostly) friendly Opministrator]]></description><link>https://blog.sprlng.de/</link><image><url>https://blog.sprlng.de/favicon.png</url><title>the (mostly) friendly Opministrator</title><link>https://blog.sprlng.de/</link></image><generator>Ghost 5.76</generator><lastBuildDate>Sun, 03 May 2026 11:28:32 GMT</lastBuildDate><atom:link href="https://blog.sprlng.de/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Shape your cv in latex]]></title><description><![CDATA[<p><strong>TLDR;</strong> open up <a href="https://gitlab.com/alexandersperling/moderncv-boilerplate?ref=blog.sprlng.de">https://gitlab.com/alexandersperling/moderncv-boilerplate</a>, fork the repository and follow the instructions.</p><p>I don&apos;t know about you, but when starting with <a href="https://github.com/moderncv/moderncv?ref=blog.sprlng.de" rel="noreferrer">moderncv</a> on Latex it was not possible for me to really get a hand on how to get this thing up and running or</p>]]></description><link>https://blog.sprlng.de/latex/</link><guid isPermaLink="false">68c97845c155660001ddf24e</guid><dc:creator><![CDATA[Alexander Sperling]]></dc:creator><pubDate>Wed, 17 Sep 2025 09:21:43 GMT</pubDate><media:content url="https://blog.sprlng.de/content/images/2025/09/latex.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.sprlng.de/content/images/2025/09/latex.jpg" alt="Shape your cv in latex"><p><strong>TLDR;</strong> open up <a href="https://gitlab.com/alexandersperling/moderncv-boilerplate?ref=blog.sprlng.de">https://gitlab.com/alexandersperling/moderncv-boilerplate</a>, fork the repository and follow the instructions.</p><p>I don&apos;t know about you, but when starting with <a href="https://github.com/moderncv/moderncv?ref=blog.sprlng.de" rel="noreferrer">moderncv</a> on Latex it was not possible for me to really get a hand on how to get this thing up and running or even create a simple pdf of it.</p><p>So after some time struggling and spending time in a lot of documentation I&apos;ve managed to setup some boilerplate repository to (hopefully) save you some time.</p><p>So what is moderncv?</p><blockquote>The <code>moderncv</code> package provides a document class for typesetting applications (curricula vitae and cover letters) in various styles. <code>moderncv</code> aims to be both straightforward to use and customizable, providing five ready-made styles (classic, casual, banking, oldstyle and fancy) and allowing you to define your own by modifying colors, fonts, icons, etc.</blockquote><p>In fact, it provides you a layout for a modern looking cv and a lot of possible options to structure and color it.</p><p>Here I&apos;m going to share some best practices (for me).</p><ol><li>Install <a href="https://www.latex-project.org/get/?ref=blog.sprlng.de" rel="noreferrer">Latex</a> for your distriution, there are a lot of different packages, usually the moderncv package is already included.</li><li>Setup your editor, in my case I use VSCode for editing tex files. With the <a href="https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop&amp;ref=blog.sprlng.de" rel="noreferrer">Latex workshop plugin</a>, you are able to compile your project on the fly and see the result directly in your editor.</li><li>Fork this <a href="https://gitlab.com/alexandersperling/moderncv-boilerplate?ref=blog.sprlng.de" rel="noreferrer">repository</a> and adapt the files to your needs. When the changes are commited to Gitlab, a CI job starts and will create your new modernCV pdf file. An example of how this looks, can be found <a href="https://vita.sprlng.de/cv_de.pdf?ref=blog.sprlng.de" rel="noreferrer">here</a>.</li></ol><p><strong>credits: </strong><br>Foto von Ron Lach : https://www.pexels.com/de-de/foto/arm-hand-reinigung-reinigen-10573240/</p>]]></content:encoded></item><item><title><![CDATA[Don't let CDK overwrite your permissions]]></title><description><![CDATA[<p></p><p>Working with <a href="https://aws.amazon.com/de/cdk/?ref=blog.sprlng.de">AWS CDK </a>, on a daily basis, grants you a lot of possibilities to automate your cloud infrastructure. However, as in every tool, there are sometimes pitfalls which are, at least for me, worth to document.<br><br>In CDK, it is possible to import IAM roles by their specific ARN</p>]]></description><link>https://blog.sprlng.de/cdk-permission-overwrite/</link><guid isPermaLink="false">6404774e970eb500015a3298</guid><category><![CDATA[cdk]]></category><category><![CDATA[cloud]]></category><dc:creator><![CDATA[Alexander Sperling]]></dc:creator><pubDate>Sun, 05 Mar 2023 12:19:04 GMT</pubDate><media:content url="https://blog.sprlng.de/content/images/2023/03/Arch_AWS-Cloud-Development-Kit_64@5x-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.sprlng.de/content/images/2023/03/Arch_AWS-Cloud-Development-Kit_64@5x-1.png" alt="Don&apos;t let CDK overwrite your permissions"><p></p><p>Working with <a href="https://aws.amazon.com/de/cdk/?ref=blog.sprlng.de">AWS CDK </a>, on a daily basis, grants you a lot of possibilities to automate your cloud infrastructure. However, as in every tool, there are sometimes pitfalls which are, at least for me, worth to document.<br><br>In CDK, it is possible to import IAM roles by their specific ARN (Amazon Resource Names, starting with <code>arn:partition...</code>).</p><p>With that you can easily, lets say during the creation of an eks cluster, set up IAM roles without any permissions and import them later on to grant rights on specific resources, for examples <a href="https://docs.aws.amazon.com/de_de/secretsmanager/latest/userguide/intro.html?ref=blog.sprlng.de">Secrets Manager Secrets</a>.</p><p>Lets assume the following setup. We have an app with 2 different stacks. In another app, We&apos;ve set up an <a href="https://external-secrets.io/v0.7.2/?ref=blog.sprlng.de">External Secrets Operator</a> helm chart with IAM Roles for ServiceAccounts and want to grant this IAM role now permissions for reading database secrets.</p><pre><code class="language-shell">bin
 &#x2514;&#x2500;&#x2500;&#x2500;app.ts 
src
 &#x251C;&#x2500;&#x2500;&#x2500;mariadb-stack.ts
 &#x2514;&#x2500;&#x2500;&#x2500;postgresql-stack.ts
packagage.json
.stuff-config.json
.
..
...</code></pre><p>In our <code>mariadb-stack.ts</code> we define the following.</p><pre><code class="language-typescript">import { App, Stack, StackProps } from &apos;aws-cdk-lib&apos;;
import { Vpc } from &apos;aws-cdk-lib/aws-ec2&apos;;
import { Role } from &apos;aws-cdk-lib/aws-iam&apos;;
import { Key } from &apos;aws-cdk-lib/aws-kms&apos;;
import {
  Credentials,
  DatabaseInstance,
  DatabaseInstanceEngine,
  MariaDbEngineVersion,
} from &apos;aws-cdk-lib/aws-rds&apos;;
import { Secret } from &apos;aws-cdk-lib/aws-secretsmanager&apos;;

export interface MariaDbProps extends StackProps {}

export class MariaDbStack extends Stack {
  constructor(app: App, id: string, props: MariaDbProps) {
    super(app, id, props);

    const encryptionKey = new Key(this, &apos;MariaDbKey&apos;, {});
    const vpc = Vpc.fromLookup(this, &apos;MyVpc&apos;, {
      vpcId: &apos;vpc-01234567890abcdef&apos;,
    });

    const databaseEngine = DatabaseInstanceEngine.mariaDb({
      version: MariaDbEngineVersion.VER_10_5_9,
    });

    const databaseSecret = new Secret(this, &apos;DatabaseSecret&apos;, {
      description: &apos;my database secret&apos;,
      encryptionKey: encryptionKey,
      secretName: `myDatabaseSecret`,
    });

    new DatabaseInstance(this, &apos;MariaDbDatabase&apos;, {
      engine: databaseEngine,
      vpc,
      credentials: Credentials.fromSecret(databaseSecret),
    });

    const serviceAccountRoleExternalSecrets = Role.fromRoleArn(
      this,
      &apos;ExternalSecretsRole&apos;,
      &apos;arn:eu-west-1:iam::123456789:role/MyExternalSecretsOperator&apos;,
    );
    databaseSecret.grantRead(serviceAccountRoleExternalSecrets);
  }
}
</code></pre><p>Our <code>postgresql-stack.ts</code> looks like this</p><pre><code class="language-typescript">import { App, Stack, StackProps } from &apos;aws-cdk-lib&apos;;
import { Vpc } from &apos;aws-cdk-lib/aws-ec2&apos;;
import { Role } from &apos;aws-cdk-lib/aws-iam&apos;;
import { Key } from &apos;aws-cdk-lib/aws-kms&apos;;
import {
  Credentials,
  DatabaseInstance,
  DatabaseInstanceEngine,
  PostgresEngineVersion,
} from &apos;aws-cdk-lib/aws-rds&apos;;
import { Secret } from &apos;aws-cdk-lib/aws-secretsmanager&apos;;

export interface PostgresqlProps extends StackProps {}

export class PostgresqlStack extends Stack {
  constructor(app: App, id: string, props: PostgresqlProps) {
    super(app, id, props);

    const encryptionKey = new Key(this, &apos;PostgresqlKey&apos;, {});
    const vpc = Vpc.fromLookup(this, &apos;MyVpc&apos;, {
      vpcId: &apos;vpc-01234567890abcdef&apos;,
    });

    const databaseEngine = DatabaseInstanceEngine.postgres({
      version: PostgresEngineVersion.VER_14,
    });

    const databaseSecret = new Secret(this, &apos;DatabaseSecret&apos;, {
      description: &apos;my database secret&apos;,
      encryptionKey: encryptionKey,
      secretName: `myDatabaseSecret`,
    });

    new DatabaseInstance(this, &apos;PostgresqlDatabase&apos;, {
      engine: databaseEngine,
      vpc,
      credentials: Credentials.fromSecret(databaseSecret),
    });

    const serviceAccountRoleExternalSecrets = Role.fromRoleArn(
      this,
      &apos;ExternalSecretsRole&apos;,
      &apos;arn:eu-west-1:iam::123456789012:role/MyExternalSecretsOperator&apos;,
    );
    databaseSecret.grantRead(serviceAccountRoleExternalSecrets);
  }
}
</code></pre><p>nearly the same, I know, but it is important that his code comes from 2 different stacks. :)</p><p>The interesting part is where we import the IAM role and grant read permissions for the database secret.</p><pre><code class="language-typescript">    const serviceAccountRoleExternalSecrets = Role.fromRoleArn(
      this,
      &apos;ExternalSecretsRole&apos;,
      &apos;arn:eu-west-1:iam::123456789012:role/MyExternalSecretsOperator&apos;,
    );
    databaseSecret.grantRead(serviceAccountRoleExternalSecrets);
  }</code></pre><p>While this code works it will create one policy and every time we import that role again in another stack and granting permissions to it, this previous policy will be overwritten. When first humbling around this I really wondered why the f***, the service was able to fetch that secret in a previous step but not afterward, until examining the resulting policy and related cdk diff.</p><p>Obviously I was not the only one with that problem and so AWS already fixed that in <a href="https://github.com/aws/aws-cdk/pull/20705?ref=blog.sprlng.de">version 2.29</a>. So the way to solve this would look like this. You add a new property to define a default policy name so that different policies per stack will be created.</p><p>The resulting code should look like this.</p><p><code>postgresql-stack.ts</code></p><pre><code class="language-typescript">    const serviceAccountRoleExternalSecrets = Role.fromRoleArn(
      this,
      &apos;ExternalSecretsRole&apos;,
      &apos;arn:eu-west-1:iam::123456789012:role/MyExternalSecretsOperator&apos;,
      {
        defaultPolicyName: &apos;PostgreSqlStackPolicy&apos;,
      },
    );
    databaseSecret.grantRead(serviceAccountRoleExternalSecrets);
    </code></pre><p><code>mariadb-stack.ts</code></p><pre><code class="language-typescript">    const serviceAccountRoleExternalSecrets = Role.fromRoleArn(
      this,
      &apos;ExternalSecretsRole&apos;,
      &apos;arn:eu-west-1:iam::123456789012:role/MyExternalSecretsOperator&apos;,
      {
        defaultPolicyName: &apos;MariaDbStackPolicy&apos;,
      },
    );
    databaseSecret.grantRead(serviceAccountRoleExternalSecrets);</code></pre><p></p>]]></content:encoded></item><item><title><![CDATA[Ghost up and running in Docker]]></title><description><![CDATA[<p>This is mainly for documenting the process for getting this blog thing up and running on my server.</p><p>When looking for potential blog software and researching cms systems I stumbled over <a href="https://ghost.org/?ref=blog.sprlng.de">ghost </a>quite early. Starting in 2013 ghost aimed to be a fairly minimal cms for quick and easy blog</p>]]></description><link>https://blog.sprlng.de/ghost-up-and-running-in-docker/</link><guid isPermaLink="false">63e902015433f2000111f648</guid><category><![CDATA[docker]]></category><category><![CDATA[ghost]]></category><dc:creator><![CDATA[Alexander Sperling]]></dc:creator><pubDate>Sun, 12 Feb 2023 16:35:02 GMT</pubDate><media:content url="https://blog.sprlng.de/content/images/2023/02/ghost-g7f8defd08_640.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.sprlng.de/content/images/2023/02/ghost-g7f8defd08_640.png" alt="Ghost up and running in Docker"><p>This is mainly for documenting the process for getting this blog thing up and running on my server.</p><p>When looking for potential blog software and researching cms systems I stumbled over <a href="https://ghost.org/?ref=blog.sprlng.de">ghost </a>quite early. Starting in 2013 ghost aimed to be a fairly minimal cms for quick and easy blog setups.</p><p>Since I was looking for something like this, I decided to give it a try. Another big plus for me is the possibility to run the whole setup in Docker. I like the basic pattern of running services in containers on my server exposed locally and use nginx as reverse proxy to foreward the traffic to those services.</p><p>Lets see what we need to do.</p><p>First of lets make sure we have docker installed and running.</p><pre><code class="language-shell">$ docker --version
Docker version 23.0.1, build a5ee5b1</code></pre><p>When using containers the storage is usually ephemeral, that means when the container is stopped for whatever reason the data is gone. We do not want this for our blog so we have to put in some persistence for storing the data.</p><pre><code class="language-shell"># creating the volume for our blog itself
docker volume create ghostblog

# creating the volume for the blog database
docker volume create ghostblogdb</code></pre><p>We can now start setting up a docker-compose file where the configuration of our containers will happen.</p><p><strong>TLDR;</strong></p><p>this is the whole file which I used which should work, when the volumes were created beforehand. Following we will take a look into separate parts of this.</p><pre><code class="language-yaml">version: &apos;3&apos;
services:
  ghost:
    image: ghost:5-alpine
    restart: always
    ports:
      - &quot;127.0.0.1:8888:2368&quot;
    environment:
      database__client: mysql
      database__connection__host: db
      database__connection__user: root
      database__connection__password: myS3cur3Pa$$W0rd)
      database__connection__database: ghost
      url: https://this-should-be-your-url.tld/
    volumes:
      - ghostblog:/var/lib/ghost/content
    depends_on:
      - db
  db:
    image: mysql:8.0
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: myS3cur3Pa$$W0rd)
    volumes:
      - ghostblogdb:/var/lib/mysql
volumes:
  ghostblog:
  ghostblogdb:</code></pre><p></p><p>In the first part we define the service ghost itself. The important part can be found in the environment section.</p><p>Ghost itself can be configured via environment variables, where nested keys can be combined via two underscores with their top level keys. We define in our setup mainly the database options and the important key which defines our url.</p><p>When this is not set correctly ghost will fail to preview your site in the admin and editor area.</p><p>Setting the password can be probably also done via <a href="https://docs.docker.com/engine/swarm/secrets/?ref=blog.sprlng.de">Docker Secrets</a>. This has to be created beforehand and needs a separate declaration in the docker-compose.yaml. There is a pretty good blog entry on this by Allan MacGregor on <a href="https://earthly.dev/blog/docker-secrets/?ref=blog.sprlng.de">Earthly</a>.</p><p>However since this database is running only locally exposed and I do not put this file under version control, I consider this as &apos;secure enough&apos;.</p><pre><code class="language-yaml">[...]
environment:
  database__client: mysql
  database__connection__host: db
  database__connection__user: root
  database__connection__password: myS3cur3Pa$$W0rd)
  database__connection__database: ghost
  url: https://this-should-be-your-url.tld/
volumes:
  - ghostblog:/var/lib/ghost/content
depends_on:
  - db</code></pre><p>Also we define the volume which was created in the beginning where our content will be stored. I added the for the dependency because when experimenting with the setup I saw very often error messages of ghost itself in the log, that the database connection failed, which came due to the fact that the container was not running yet.</p><p>When starting up those containers you should be able to access the ghost default interface on your localhost on port 8888.<br>In the next part, we will take a look how this works all together with nginx as reverse proxy.</p><p><strong>credits</strong><br>Image by <a href="https://pixabay.com/users/clker-free-vector-images-3736/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=303596">Clker-Free-Vector-Images</a> from <a href="https://pixabay.com//?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=303596">Pixabay</a><br><a href="https://hub.docker.com/_/ghost?ref=blog.sprlng.de">https://hub.docker.com/_/ghost</a><br><a href="https://ghost.org/docs/config/?ref=blog.sprlng.de">https://ghost.org/docs/config/</a></p>]]></content:encoded></item></channel></rss>