Skip to content

Commit 8523e10

Browse files
committed
Add post about leveling up one's serverless game
1 parent 5e1e53d commit 8523e10

5 files changed

Lines changed: 213 additions & 20 deletions

File tree

.github/workflows/main.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ jobs:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- name: Set up Node.js
14-
uses: actions/setup-node@v4
14+
uses: actions/setup-node@v5
1515
- name: Checkout repo
16-
uses: actions/checkout@v4
16+
uses: actions/checkout@v5
1717
- name: Install Prettier
1818
run: sudo npm install --global prettier
1919
- name: Lint
@@ -24,10 +24,10 @@ jobs:
2424
needs: lint
2525
runs-on: ubuntu-latest
2626
env:
27-
HUGO_VERSION: 0.148.2
27+
HUGO_VERSION: 0.150.1
2828
steps:
2929
- name: Checkout repo
30-
uses: actions/checkout@v4
30+
uses: actions/checkout@v5
3131
with:
3232
submodules: recursive
3333
- name: Install Hugo
@@ -37,7 +37,7 @@ jobs:
3737
- name: Build
3838
run: hugo --minify
3939
- name: Upload artifact
40-
uses: actions/upload-pages-artifact@v3
40+
uses: actions/upload-pages-artifact@v4
4141
with:
4242
path: ./public
4343

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
themes/

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.PHONY: build
2+
build:
3+
hugo
4+
5+
.PHONY: run
6+
run:
7+
hugo server -w
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
---
2+
title: Level Up Your Serverless Game
3+
author: Lena Fuhrimann
4+
comments: true
5+
date: 2025-09-25
6+
---
7+
8+
Is your serverless project running into unexpected snags? You're not alone. From
9+
basic misconfigurations to frustrating deployment nightmares, we've seen the
10+
same pitfalls trap countless teams. This guide is your cheat sheet to avoiding
11+
those common mistakes and accelerating your journey to serverless mastery. Think
12+
of each level as a lesson learned—a mistake you don't have to make. From basic
13+
misconfigurations to complex deployment nightmares, the patterns are
14+
frustratingly consistent. We've compiled this blog post to help you avoid these
15+
common pitfalls and accelerate your journey to serverless excellence. Think of
16+
each level as a lesson learned, a mistake you don't have to make. To learn more
17+
about the details and apply the learnings yourself, follow our open source
18+
[Serverless Workshop](https://github.com/bespinian/serverless-workshop). This
19+
blog post as well as the workshop both use AWS Lambda but the learnings can be
20+
applied to any function as a service (FaaS) platform. Now, let's dive in.
21+
22+
## Level 0 - This is easy!
23+
24+
**⚠️ Mistake: None yet**
25+
26+
To get started, we need to create our first function. Many projects jump into
27+
complex architectures without understanding the fundamentals, leading to
28+
confusion and wasted effort. This level is about demystifying the basic
29+
mechanics of serverless: understanding how to create a function, configure its
30+
runtime, and pass parameters using environment variables. You should also get
31+
acquainted with your cloud provider's permission model, a cornerstone of secure
32+
serverless applications. By adding an HTTP trigger through an API gateway, you
33+
can transform our function into a web-accessible service, making it tangible and
34+
interactive.
35+
36+
**✅ Start as simple and small as possible and take it from there.**
37+
38+
## Level 1: Loggin' it!
39+
40+
**⚠️ Mistake: Lack of structured logging**
41+
42+
Having deployed our first function, we have no idea what it does and if it
43+
succeeds or not. When things go wrong, it's very hard to debug and investigate
44+
issues. Many serverless projects suffer from poor observability, making
45+
troubleshooting a nightmare. Using structured logging (e.g., formatted as JSON),
46+
we get a set of logs that we can query and which give us custom insights into
47+
what our function does. We'll know if it succeeds or fails in doing so and get
48+
context about what went wrong if things don't go as expected. Such a log line
49+
could look as follows:
50+
51+
```json
52+
{
53+
"correlationId": "9ac54d82-75e0-4f0d-ae3c-e84ca400b3bd",
54+
"requestId": "58d9c96e-ae9f-43db-a353-c48e7a70bfa8",
55+
"commitHash": "9d9154e",
56+
"level": "INFO",
57+
"requestPath": "/users/1",
58+
"requestMethod": "GET",
59+
"responseCode": 200,
60+
"responseBody": "All good"
61+
}
62+
```
63+
64+
**✅ Don't wait until production issues arise to implement structured logging.**
65+
66+
## Level 2: Tracin' it!
67+
68+
**⚠️ Mistake: Lack of distributed tracing**
69+
70+
As your serverless applications grow, understanding the flow of requests becomes
71+
critical. Distributed tracing provides invaluable insights into performance
72+
bottlenecks and errors. Most cloud providers provide comprehensive toolsets to
73+
easily add tracing IDs and even trace HTTP calls and other function calls by
74+
just adding a couple of lines of code. Therefore, it's usually quite cheap to
75+
add tracing and the value it provides is quite large. A low-hanging fruit you
76+
shouldn't miss out on.
77+
78+
**✅ Activate distributed tracing for all your functions.**
79+
80+
## Level 3: Timin' it!
81+
82+
**⚠️ Mistake: Function timeouts not handled gracefully**
83+
84+
Functions operate within time constraints, making it essential to handle
85+
timeouts gracefully. Unhandled timeouts can lead to unexpected behavior and data
86+
inconsistencies. We need to ensure our functions terminate cleanly and
87+
predictably, even when approaching their time limits. To achieve that, simply
88+
monitor the remaining execution time and implement mechanisms for aborting
89+
long-running operations with enough time left. This allows to either perform a
90+
proper cleanup or partial results instead, ensuring a smooth and reliable user
91+
experience.
92+
93+
**✅ Don't let timeouts cause chaos in your application.**
94+
95+
## Level 4: Optimized Cold Starts!
96+
97+
**⚠️ Mistake: Ignoring cold start performance**
98+
99+
Cold starts, the initial latency experienced when a Lambda function is invoked
100+
after a period of inactivity, can impact performance. In latency-sensitive
101+
applications, even a few extra milliseconds can be noticeable to users. This
102+
level explores techniques to minimize cold start times, such as moving
103+
initialization code outside the handler. By optimizing our function's startup
104+
process, we can enhance responsiveness and improve the overall user experience.
105+
106+
**✅ Optimize for cold starts early to avoid performance bottlenecks later.**
107+
108+
## Level 5: Decoupled!
109+
110+
⚠️ Mistake: Tight coupling
111+
112+
As your serverless applications grow, the need for asynchronous communication
113+
becomes essential. Without it, services are often tightly coupled, meaning a
114+
failure in one can cause a domino effect of failures throughout your entire
115+
system. This level introduces Amazon SQS, a simple yet powerful message queuing
116+
service. Think of it as a waiting room for messages; your function can drop off
117+
a message and move on to other tasks, while another function can pick it up
118+
later when it's ready. This decoupling creates more scalable and resilient
119+
architectures, making your application much more robust.
120+
121+
✅ Use message queues like SQS to decouple services and build a more resilient
122+
system.
123+
124+
## Level 6: Infrastructure as Code!
125+
126+
⚠️ Mistake: Manual infrastructure management
127+
128+
Managing cloud resources manually is slow, inconsistent, and highly prone to
129+
human error. It's a recipe for chaos, especially as your application scales.
130+
This level is all about adopting Infrastructure as Code (IaC) using a tool like
131+
Terraform. By defining your serverless functions and their dependencies in a
132+
configuration file, you can treat your infrastructure like any other code. This
133+
means repeatable, version-controlled deployments that are consistent across all
134+
your environments.
135+
136+
✅ Stop clicking around in the console and start managing your infrastructure
137+
with code.
138+
139+
## Level 7: Testing it All!
140+
141+
⚠️ Mistake: Lack of testing
142+
143+
Developing without a safety net is risky business. Untested code is a disaster
144+
waiting to happen and can lead to bugs, unexpected behavior, and production
145+
outages. This level tackles the critical practice of unit testing and local
146+
execution for your functions. By writing tests that check your code's behavior
147+
and running them locally, you can catch errors early and ensure your functions
148+
are reliable before they ever hit production.
149+
150+
✅ Don't just hope your code works—test it to be sure.
151+
152+
## Level 8: Securin it!
153+
154+
⚠️ Mistake: Overly permissive roles
155+
156+
Giving a function more permissions than it needs is like handing out a master
157+
key to your entire house. It creates a massive security vulnerability. This
158+
level is about applying the principle of least privilege, which means granting
159+
your functions only the absolute minimum permissions required to do their job.
160+
By meticulously defining and restricting access, you significantly reduce the
161+
potential damage from a security breach and strengthen your application's
162+
overall security posture.
163+
164+
✅ Implement the principle of least privilege to lock down your serverless
165+
functions.
166+
167+
## Level 9: The Gradual Rollout!
168+
169+
⚠️ Mistake: Risky deployments
170+
171+
Deploying a new version of your application to 100% of your users at once is a
172+
high-stakes gamble. If there's a problem, everyone feels the pain immediately.
173+
This level introduces a safer approach: canary deployments. Using a service like
174+
AWS CodeDeploy, you can automatically release a new version to a small subset of
175+
your users first. If the new version performs well, you can gradually roll it
176+
out to the rest. If not, you can roll it back quickly, minimizing the impact of
177+
any errors.
178+
179+
✅ Minimize deployment risk by gradually rolling out new changes to your users.

static/custom.css

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
font-weight: 400;
66
src: url(https://fonts.gstatic.com/s/lato/v17/S6uyw4BMUTPHjxAwXjeu.woff2)
77
format("woff2");
8-
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
9-
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
8+
unicode-range:
9+
U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113,
10+
U+2C60-2C7F, U+A720-A7FF;
1011
}
1112
/* latin */
1213
@font-face {
@@ -15,9 +16,10 @@
1516
font-weight: 400;
1617
src: url(https://fonts.gstatic.com/s/lato/v17/S6uyw4BMUTPHjx4wXg.woff2)
1718
format("woff2");
18-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
19-
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
20-
U+FEFF, U+FFFD;
19+
unicode-range:
20+
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
21+
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
22+
U+FFFD;
2123
}
2224
/* latin-ext */
2325
@font-face {
@@ -27,8 +29,9 @@
2729
font-display: swap;
2830
src: url(https://fonts.gstatic.com/s/lato/v17/S6u_w4BMUTPHjxsI5wq_FQft1dw.woff2)
2931
format("woff2");
30-
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
31-
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
32+
unicode-range:
33+
U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113,
34+
U+2C60-2C7F, U+A720-A7FF;
3235
}
3336
/* latin */
3437
@font-face {
@@ -38,9 +41,10 @@
3841
font-display: swap;
3942
src: url(https://fonts.gstatic.com/s/lato/v17/S6u_w4BMUTPHjxsI5wq_Gwft.woff2)
4043
format("woff2");
41-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
42-
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
43-
U+FEFF, U+FFFD;
44+
unicode-range:
45+
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
46+
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
47+
U+FFFD;
4448
}
4549
/* latin-ext */
4650
@font-face {
@@ -50,8 +54,9 @@
5054
font-display: swap;
5155
src: url(https://fonts.gstatic.com/s/lato/v17/S6u9w4BMUTPHh6UVSwaPGR_p.woff2)
5256
format("woff2");
53-
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
54-
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
57+
unicode-range:
58+
U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113,
59+
U+2C60-2C7F, U+A720-A7FF;
5560
}
5661
/* latin */
5762
@font-face {
@@ -61,9 +66,10 @@
6166
font-display: swap;
6267
src: url(https://fonts.gstatic.com/s/lato/v17/S6u9w4BMUTPHh6UVSwiPGQ.woff2)
6368
format("woff2");
64-
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
65-
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
66-
U+FEFF, U+FFFD;
69+
unicode-range:
70+
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
71+
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
72+
U+FFFD;
6773
}
6874

6975
:root:not(.light) {

0 commit comments

Comments
 (0)