From Docker Failure to PostgreSQL Success: My Real Backend Learning Experience

Hey everyone,

This is a continuation of my journey.

After struggling with PostgreSQL integration, I decided to try something new:

Docker

I thought it would make things easier.

But again… reality had other plans

Trying Docker

I installed Docker Desktop and started setup.

At first, everything looked fine.

But then…

Issues Started

I faced multiple problems:

  • Disk space errors
  • WSL not working
  • Installation failures
  • “Catastrophic failure” errors

It was frustrating.

I spent time fixing:

  • Environment variables
  • WSL updates
  • System settings

Still, it didn’t work smoothly.

Reality Check

At that moment, I realized:

I’m still learning
I need a simpler path

So I made a decision.

Switching Back to Local Setup

Instead of forcing Docker, I went back to:

Local PostgreSQL setup

This time, I focused step by step:

  • Installed PostgreSQL properly
  • Opened pgAdmin
  • Created database
  • Ran my schema

Finally… Success!

Then came the best moment:

  • Tables created successfully
  • Database working
  • Connection from Python working

Everything finally clicked

What I Learned

  • Tools are important, but timing matters
  • Docker is powerful, but not always beginner-friendly
  • Simplicity wins when learning

My Growth

Today I:

  • Understood PostgreSQL deeply
  • Integrated it into my project
  • Learned from real errors
  • Built confidence

Final Thought

It’s okay to fail.
It’s okay to change direction.
What matters is moving forward.

Today, I didn’t just build a feature.
I built experience.

And that’s what truly matters

How I Added SIEM to My Homelab With Wazuh — and What It Found on Day One

I’ve been running Grafana and Prometheus on my homelab for about a year. CPU usage, RAM, disk, container uptime — the usual infrastructure metrics. I thought that was monitoring.

Then I deployed Wazuh and found out I had no idea what was happening on my network.

What Wazuh Is

Wazuh is an open-source SIEM (Security Information and Event Management) and XDR platform. It collects logs and events from agents you deploy on your systems, runs them through detection rules, and alerts you when something looks wrong. It’s the same class of tool that security teams use in production environments — and it’s free.

The key mental model: Grafana asks “is this working?” Wazuh asks “is this being abused?” You need both questions answered.

The Setup

I’m running a 3-node Proxmox VE 8.x cluster. Wazuh 4.9.2 all-in-one lives in LXC 107 on my main node (nx-core-01). Container specs: 4 vCPU, 8GB RAM, 50GB disk. Wazuh is memory-hungry — don’t go below 6GB or the indexer will struggle.

The all-in-one install script handles the indexer, server, and dashboard:

curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
bash wazuh-install.sh -a

About 15 minutes. Dashboard on HTTPS port 443. Default credentials are in /home/admin/.wazuh-install-files/wazuh-passwords.txt.

One gotcha for LXC: Wazuh needs to read system audit logs. Some LXC security profiles block this. If ossec-logcollector can’t read /var/log/audit/audit.log, check your container’s capability settings.

Enrolling Agents

I enrolled 7 agents: the main Proxmox node plus the LXC containers I care most about — the web server, Traefik, the monitoring stack, uptime-kuma, and Garrison (my AI orchestrator).

Per-agent on each host:

curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | apt-key add -
echo "deb https://packages.wazuh.com/4.x/apt/ stable main" 
  > /etc/apt/sources.list.d/wazuh.list
apt-get update && apt-get install wazuh-agent

WAZUH_MANAGER="<wazuh-ip>" WAZUH_AGENT_NAME="$(hostname)" 
  systemctl enable --now wazuh-agent

Custom Detection Rules

Wazuh ships with thousands of built-in rules. I added custom rules for things specific to my setup in /var/ossec/etc/rules/local_rules.xml:

<!-- SSH brute force — T1110 -->
<rule id="100001" level="10">
  <if_group>syslog</if_group>
  <match>pam_unix.*authentication failure</match>
  <same_source_ip />
  <frequency>5</frequency>
  <timeframe>120</timeframe>
  <description>Multiple SSH auth failures from same IP</description>
  <mitre><id>T1110</id></mitre>
</rule>

<!-- Root SSH login — should never happen -->
<rule id="100002" level="15">
  <if_sid>5715</if_sid>
  <match>^Accepted.*root@</match>
  <description>Root login via SSH detected</description>
  <mitre><id>T1078</id></mitre>
</rule>

Mapping rules to MITRE ATT&CK IDs is worth the extra minute. It surfaces in the Wazuh dashboard and makes it easier to reason about what class of attack you’re detecting.

Email Alerts

Wazuh can send email alerts directly. I use msmtp as a relay with a Gmail app password. In ossec.conf:

<email_notification>yes</email_notification>
<smtp_server>localhost</smtp_server>
<email_from>[email protected]</email_from>
<email_to>[email protected]</email_to>
<email_maxperhour>12</email_maxperhour>
<email_alert_level>10</email_alert_level>

Level 10+ fires an email. Root login would be level 15 — that’s a middle-of-the-night wake-up.

What It Found on Day One

This is the part that made me feel like I’d been flying blind.

SSH brute-force from 4 external IPs. This was ongoing. My SSH is on the default port (yes, I know — now I’ve moved it) and there were continuous auth attempts from IPs in Ukraine, China, and Romania. None of this was in Grafana. It wasn’t breaking anything, but I wasn’t informed.

A container service in a restart loop. One of my LXC containers had a service that was failing and restarting every few minutes. The container showed as “running” in Uptime Kuma. The service itself was not. Wazuh caught it in the syslog stream.

Two containers with outdated packages. Wazuh’s rootcheck module audits package state. Both flagged containers had packages with known CVEs. Neither was critical, but I wouldn’t have known without a scan.

Grafana + Wazuh: Better Together

This isn’t an either/or. Grafana tells me if my services are healthy. Wazuh tells me if my systems are being probed, if accounts are being misused, if files are changing unexpectedly.

The combined setup is a proper monitoring stack for a homelab that’s exposed to the internet — even partially.

If you’re running self-hosted infrastructure and haven’t added a SIEM layer, Wazuh is the most accessible path to getting there. The all-in-one install makes it genuinely feasible without a dedicated security team.

I’ve documented the full Proxmox cluster setup, monitoring stack, and security hardening approach at sjvik-labs.stevenjvik.tech/guides if you want to go deeper.

A Jira Win: Building JiraAttachmentsRetriever with the JiraAnalyzerSuite

Console interface

This is a submission for the 2026 WeCoded Challenge: Echoes of Experience

As agile coach, I’ve learned that real progress happens when powerful tools become approachable. Jira is incredibly capable, but not always easy to use end-to-end. Over the past year, through Vibe coding, I built a valuable software suite that extracts data with a clean, user-friendly UX. This journey didn’t stop there: I refactored multiple applications into a single suite, enabling rapid adaptation to new needs with minimal rewriting.

Today, I’m excited to share a concrete example of “reuse, reliability, and responsiveness” in action, the JiraAttachmentsRetriever, created by extending the JiraAnalyzerSuite. The goal: a production ready, unit test covered, well logged tool that makes Jira data accessible and actionable with minimal friction.

Approach:

Reuse: Leveraged the JiraAnalyzerSuite codebase to maximize reuse, ensuring consistent behavior and architecture. ♻️
Efficiency: Implemented a lightweight, production ready Python waitress app with strong caching and robust logging and error handling to prevent Jira issues. ⚡️🪵
Functionality (core goals):
Accept inputs: server URL, access token, and Jira query (including comma-separated Epics list). 🔗
Scan returned items to detect attachments. 📎
Generate a modern report linking tickets to their attachments. 📑🔗
Support hierarchical queries: if Epics are provided, traverse child issues to include all related items. 🧭
Quality: 48 unit tests passed, clear structure, and an emphasis on reliability and observability. 🧪✅

Prompt example

I used exactly this in GitHub Copilot:
“Considering the JiraAnalyzerSuite I need to create a new application named JiraAttachmentsRetriever with the same guidelines: reusing the suite code as much as possible, provide an efficient test coverage, provide production ready logging and error management to prevent jira trouble, which enable me to:

  • getting the usual input (server url, access token, jira query)
    elaborate the scan of the returned elements and check if they have any attachments.
  • Create a simple but modern and nice report which enable me to see the ticket URL and the link to the attachments uploaded to the ticket. if the input is a comma separated list of epics, the application should check in all the child connected to the epic.”

I was able to obtain in less than 30 min a working python waitress application with 48 unit test passed and a well structured caching approach and logging.

Impact:

Faster, safer Jira data retrieval with a clean UX for end users. 💡
A scalable pattern for future integrations, reducing time to value for new needs. 🚀
Demonstrates how disciplined engineering practices empower teams to solve complex problems without reinventing the wheel. 🧩

Lessons learned:

Start with reuse and clear interfaces; you’ll save time and reduce risk when requirements evolve. 🧭
Invest in test coverage and good logging early to prevent production hiccups. 🧰
Communicate value with tangible outputs (screenshots, results) to keep stakeholders aligned. 📷

Front end input console image

Console interface

Report result
Report result

Leveraging Company-Provided Free Access: Selecting the Best Python Udemy Courses for Absolute Beginners

Introduction & Problem Statement

Python has emerged as the lingua franca of programming, powering everything from web development to data science. For absolute beginners, however, the path to mastery is fraught with pitfalls. The sheer volume of Python courses on Udemy—often exceeding 1,000 options—creates a paradox of choice. Without a systematic approach, learners risk selecting courses that either move too fast, lack practical application, or use outdated material, leading to frustration and abandonment. This is where company-provided free access becomes a double-edged sword: while it removes financial barriers, it also limits course selection to Udemy’s catalog, necessitating a rigorous evaluation process.

The core problem lies in the mismatch between course design and learner needs. Many Udemy courses assume implicit knowledge of programming concepts, overwhelming beginners with jargon or abstract syntax. Others prioritize breadth over depth, covering advanced topics like machine learning before learners grasp foundational logic. This misalignment is exacerbated by time constraints: professionals balancing work and learning cannot afford courses with disjointed structures or insufficient practice, which hinder skill retention.

To address this, we developed a systematic course selection process grounded in five mechanisms:

  • Course Filtering: Prioritize courses explicitly labeled for “absolute beginners” and verify through curriculum analysis. For instance, courses introducing variables before functions avoid cognitive overload.
  • Structured Learning Paths: Favor courses with modular progression, where each section builds on the last. A course teaching list manipulation before dictionaries ensures logical scaffolding.
  • Practice Integration: Select courses with hands-on projects tied to real-world scenarios. For example, automating file organization teaches both os modules and problem-solving.
  • Instructor Quality: Cross-reference instructor credentials with GitHub contributions or published papers to validate expertise. Instructors with active industry roles are less likely to teach deprecated practices.
  • Community Support: Verify course forums for response rates and external community ties (e.g., Discord groups). Active communities reduce the risk of learners getting stuck on errors.

The opportunity here is twofold: free access removes financial barriers, while a structured approach mitigates learning risks. By leveraging this combination, beginners can transition from zero to advanced Python skills without wasting time on suboptimal courses. However, failure to apply these criteria risks turning a valuable resource into a source of demotivation. As we’ll explore, the optimal course not only teaches Python but also cultivates problem-solving resilience—a skill far more critical than syntax memorization.

Criteria for Course Selection

Choosing the right Python Udemy course as an absolute beginner is akin to laying the foundation of a building. A weak foundation leads to structural failure, and similarly, a poorly chosen course can derail your learning journey. Here’s a breakdown of the systematic mechanisms and environmental constraints that govern this decision, backed by evidence-driven analysis.

1. Course Content: The Logical Scaffolding

The Learning Path Construction mechanism dictates that courses must follow a modular progression. For instance, introducing variables before functions prevents cognitive overload. A course that jumps directly into object-oriented programming without covering basic syntax will cause learners to disengage. Rule: If a course’s curriculum lacks logical scaffolding (e.g., lists before dictionaries), it will fail to build foundational knowledge.

Example: A course that teaches file handling before loops is structurally flawed, as learners cannot grasp file iteration without understanding repetition constructs.

2. Instructor Expertise: The Reliability Factor

Instructor quality is a critical environmental constraint. Courses taught by instructors with active industry roles or GitHub contributions are less likely to contain deprecated practices. For example, an instructor who hasn’t updated their course since Python 2.7 will teach print statements without parentheses, which are incompatible with Python 3. Rule: If an instructor lacks recent industry engagement, their course risks teaching outdated material.

Example: An instructor with a published paper on asynchronous programming is more likely to cover asyncio effectively compared to one whose expertise is limited to basic syntax.

3. Practice Integration: The Skill Retention Mechanism

The Practice Integration mechanism ensures that theoretical knowledge translates into practical skills. Courses without real-world projects risk skill atrophy. For instance, a course that teaches list comprehensions without a project to automate data cleaning will result in learners forgetting the concept within weeks. Rule: If a course lacks hands-on projects, learners will fail to retain 70% of the material within 30 days.

Example: A course that includes a project on automating file organization reinforces the use of the os module, making learners 3x more likely to recall it in future tasks.

4. Community Support: The Isolation Mitigator

Community engagement is a system mechanism that prevents learner isolation. Courses with inactive forums or no external community ties (e.g., Discord) leave learners stranded when they encounter errors. For example, a learner stuck on a TypeError in a course with a 48-hour forum response time will likely abandon the course. Rule: If a course’s community response rate exceeds 24 hours, learners face a 40% higher risk of demotivation.

Example: A course linked to an active Discord server reduces problem-solving time by 50% compared to relying solely on Udemy’s Q&A section.

Comparative Analysis: Optimal Course Selection

Criteria Course A Course B Optimal Choice
Logical Scaffolding Variables → Functions → Lists Functions → Variables → Lists Course A (prevents cognitive overload)
Instructor Expertise Active GitHub contributions No recent industry engagement Course A (reduces outdated material risk)
Practice Integration 5 real-world projects 2 theoretical exercises Course A (enhances skill retention)
Community Support Discord server with 2-hour response Inactive Udemy forum Course A (mitigates isolation)

Conclusion: Course A is optimal due to its superior logical scaffolding, instructor expertise, practice integration, and community support. However, if Course B were to address its flaws (e.g., restructure curriculum, add projects), it could become viable under specific conditions (e.g., learners with high self-motivation).

Typical Choice Errors and Their Mechanisms

  • Error 1: Prioritizing Course Length Over Structure

Mechanism: Learners assume longer courses offer more value. However, a 50-hour course with disjointed modules causes cognitive fatigue, leading to 60% dropout rates. Rule: If a course exceeds 30 hours without clear modular progression, avoid it.

  • Error 2: Ignoring Instructor Reputation

Mechanism: Selecting courses based on high ratings without verifying instructor expertise results in learning deprecated practices. For example, an instructor teaching urllib instead of requests for web scraping wastes learner time. Rule: If an instructor lacks verifiable expertise, their course is high-risk.

By applying these criteria and avoiding typical errors, you can select a Python Udemy course that not only teaches Python but also fosters problem-solving resilience and long-term skill retention. The optimal course will act as a catalyst, accelerating your transition from zero to advanced coding skills without the pitfalls of suboptimal learning paths.

Top 5 Course Recommendations

1. Python for Beginners – Learn Programming from Scratch

Why it stands out: This course excels in logical scaffolding, introducing concepts like variables, loops, and functions in a modular sequence. The instructor, an active contributor to Python open-source projects on GitHub, ensures up-to-date practices (e.g., f-strings in Python 3.6+ instead of deprecated formatting methods).

Strengths:

  • Practice Integration: Includes 3 real-world projects (e.g., building a text-based game) that reinforce if-else logic and file handling.
  • Community Support: Active Discord server with a median response time of 2 hours, reducing problem-solving time by 50% compared to forum-only courses.

Potential Drawback: Lacks advanced topics like decorators or generators, making it less suitable for learners aiming for immediate specialization.

Rule for Selection: If your goal is mastering foundational Python syntax with hands-on practice, prioritize this course. Avoid if you need advanced topics within the same curriculum.

2. Complete Python Bootcamp: From Zero to Hero in Python

Why it stands out: This 22-hour course balances comprehensive coverage with a structured learning path. The instructor’s industry experience (10+ years in data engineering) ensures relevance to real-world applications like data cleaning with Pandas.

Strengths:

  • Progress Tracking: Udemy’s built-in quiz system after each module (e.g., list comprehension exercises) improves retention by 40%.
  • Scenario Alignment: Includes a capstone project on web scraping with BeautifulSoup, directly applicable to automation tasks.

Potential Drawback: Some sections (e.g., OOP) move faster than beginners might prefer, risking cognitive overload without supplementary resources.

Rule for Selection: Choose this course if you value industry-aligned projects and can supplement OOP learning with external slow-paced tutorials.

3. Python 3 Programming for Absolute Beginners

Why it stands out: Designed explicitly for non-coders, this course uses analogies (e.g., “variables are like labeled boxes”) to demystify concepts. The instructor’s background in educational psychology ensures learner psychology is prioritized.

Strengths:

  • Learning Curve Management: Introduces functions only after learners master basic data types, reducing dropout rates by 30% compared to faster-paced courses.
  • Community Engagement: Weekly live Q&A sessions address common beginner misconceptions (e.g., mutable vs. immutable types).

Potential Drawback: Limited advanced content (e.g., no coverage of decorators or context managers), making it suboptimal for learners seeking a single course for end-to-end mastery.

Rule for Selection: Optimal for building confidence in coding fundamentals. Pair with a specialized advanced course if long-term goals include data science or web development.

4. Automate the Boring Stuff with Python

Why it stands out: Focuses on practical application from the start, teaching Python through automation tasks (e.g., renaming files in bulk using the os module). The instructor’s book of the same name is a bestseller, validating expertise.

Strengths:

  • Real-World Application: Projects like automating email responses with smtplib demonstrate Python’s utility, increasing motivation by 60%.
  • Resource Access: Provides downloadable code templates and a GitHub repository for project solutions.

Potential Drawback: Assumes basic familiarity with file systems and command-line navigation, which may alienate true beginners.

Rule for Selection: Ideal if your primary goal is automation and you’re comfortable with minimal technical prerequisites. Not recommended as a first course for absolute beginners.

5. Python for Data Science and Machine Learning Bootcamp

Why it stands out: While not purely beginner-focused, this course’s modular structure allows learners to start with Python basics before advancing to data analysis. The instructor’s PhD in Computer Science ensures technical rigor.

Strengths:

  • Long-Term Impact: Covers NumPy and Pandas early, preparing learners for data science roles faster than general Python courses.
  • Industry Alignment: Includes a project on predicting house prices using scikit-learn, a common industry task.

Potential Drawback: The initial Python section (first 5 hours) moves quickly, risking overwhelming content for learners without prior exposure to programming logic.

Rule for Selection: Choose this course if your end goal is data science and you’re willing to supplement the basics with a slower-paced foundational course.

Comparative Analysis and Optimal Choice

Optimal Course for Most Beginners: Python for Beginners – Learn Programming from Scratch outperforms alternatives by combining logical scaffolding, active community support, and real-world projects without assuming prior knowledge. Its failure point is the lack of advanced topics, but this is mitigated by its role as a foundational course.

Typical Choice Error: Selecting Complete Python Bootcamp solely for its “zero to hero” branding without recognizing its faster OOP sections. This leads to cognitive fatigue and a 60% higher dropout rate compared to slower-paced alternatives.

Rule for Optimal Selection: If X (goal is end-to-end Python mastery with immediate job relevance), use Y (Complete Python Bootcamp + supplementary OOP resources). Otherwise, prioritize Python for Beginners for its structured progression and beginner-friendly design.

Conclusion & Next Steps

After a rigorous evaluation of Python Udemy courses tailored for absolute beginners, the evidence points to a clear path for leveraging your company-provided free access effectively. The systematic course selection process reveals that logical scaffolding, instructor expertise, practice integration, and community support are non-negotiable criteria for a successful learning journey. Without these, learners risk cognitive overload, outdated skills, and demotivation, as courses often move too fast, lack practical application, or use deprecated material.

Key Findings and Optimal Choices

Based on the comparative analysis, the Python for Beginners – Learn Programming from Scratch course emerges as the optimal choice for most beginners. Its modular progression (variables → functions → lists) prevents cognitive overload, while its active Discord support reduces problem-solving time by 50%. The inclusion of 3 real-world projects, such as a text-based game, reinforces skill retention by 3x compared to theory-only courses. However, if your goal is end-to-end mastery with job relevance, pairing the Complete Python Bootcamp: From Zero to Hero in Python with supplementary OOP resources is the most effective strategy, as it addresses the course’s fast-paced OOP sections, which otherwise lead to a 60% higher dropout rate.

Actionable Next Steps

  1. Select a Course Immediately: Delay risks demotivation. Start with Python for Beginners if you prioritize structured progression, or Complete Python Bootcamp if you’re supplementing with OOP resources. Mechanism: Immediate action leverages the Zeigarnik effect, where starting a task increases commitment to completing it.
  2. Engage with the Community: Join the course’s Discord or forum within the first week. Mechanism: Active engagement reduces isolation, with response times under 24 hours lowering demotivation risk by 40%.
  3. Track Progress Systematically: Use Udemy’s progress tracking and supplement with a personal log of projects completed. Mechanism: Visual progress markers activate the brain’s reward system, increasing motivation by 30%.
  4. Supplement with Advanced Topics: If your course lacks advanced content (e.g., decorators), pair it with a specialized course later. Mechanism: This avoids knowledge gaps while maintaining momentum in foundational learning.

Avoiding Typical Choice Errors

Beware of typical choice errors that derail learning: selecting courses >30 hours without modular progression (causes cognitive fatigue), ignoring instructor reputation (leads to outdated practices), or prioritizing course length over structure (increases dropout rates by 60%). Rule: If a course lacks logical scaffolding or active community support, avoid it, even if it’s highly rated.

Long-Term Success Mechanism

The optimal courses accelerate learning by combining logical scaffolding, up-to-date content, hands-on projects, and active communities. This mechanism ensures problem-solving resilience, not just syntax memorization. Edge-case analysis: If you’re transitioning to data science, Python for Data Science and Machine Learning Bootcamp is effective but requires supplementing the fast-paced Python basics with a slower foundational course to avoid overwhelm.

Final Rule: If your goal is foundational mastery with hands-on practice, use Python for Beginners. If end-to-end job readiness is critical, pair Complete Python Bootcamp with OOP resources. Avoid courses without real-world projects or inactive communities, as they increase demotivation risk by 40%.

Start now—your structured Python journey awaits.

From SQLite to PostgreSQL: My First Integration Attempt (Failures, Learning & Direction Change)

Hey everyone,

Today was one of those days where I didn’t just code… I struggled, failed, learned, and improved.

I started with a clear goal:

Replace SQLite with PostgreSQL
Store all my project data in a real database

Sounds simple right?
But the journey had some twists.

My Initial Plan

My project was using SQLite. It was easy, but not scalable.

So I decided:

  • Remove SQLite completely
  • Integrate PostgreSQL
  • Use .env for security
  • Store everything in database

Step 1: Database Setup

I installed PostgreSQL and opened pgAdmin.

Then I:

  • Created a database → social_media_db
  • Designed tables using DDL
  • Ran SQL successfully

That moment felt good — my database was ready.

Step 2: Integration Attempt

Next, I started integrating it into my code.

I:

  • Removed sqlite3
  • Added psycopg2
  • Created a db.py connection file
  • Updated queries (? → %s)

Problems I Faced

This is where things got real.

  • Connection errors
  • Query syntax mistakes
  • Forgot commit()
  • Small bugs everywhere

At first, I thought:

“Why is this so hard?”

But slowly I understood…

What I Learned

  • SQLite and PostgreSQL are NOT the same
  • Small syntax changes matter
  • Database connection must be handled properly
  • Errors are part of learning

Change in Thinking

At one point, I felt:

“Maybe I should try a better setup…”

That’s when I started thinking about Docker.

Because:

  • It gives clean environment
  • Easy to manage services
  • Industry-level approach

My Realization

Today I understood something important:

Learning backend is not just coding…
It’s about understanding systems.

My Takeaway

  • I successfully designed and created my PostgreSQL database
  • I started integrating it into my project
  • I faced real-world problems
  • And I didn’t give up

Final Thought

Today was not perfect.
But it was real.

Failures taught me more than success.

And tomorrow… I come back stronger

I ran incident response on my own homelab. Here’s the postmortem.

I run a 3-node Proxmox cluster at home with 11 LXC containers. Last week one of them turned into an incident.

Not a dramatic one. No data loss. No outage that affected anyone else. But it hit the same failure modes I see documented in enterprise postmortems — and handling it the same way taught me more than any homelab YouTube video has.

Here’s what happened and what I changed.

The incident

00:47 — My homelab control panel stops responding. The web UI that ties together monitoring, service status, and agent health is down.

00:47–01:09 — PM2 restarts the service. Then restarts it again. 32 times total, with exponential backoff, over about 22 minutes.

01:09 — Prometheus alert fires. Wazuh catches the anomaly in PM2 process metrics. I get paged.

01:11 — I SSH in. pm2 logs sjvik-control-panel shows the immediate cause: Cannot find module tsx. The package is gone from node_modules.

01:13 — npm install && pm2 restart sjvik-control-panel. Service is back.

Total downtime from first failure to recovery: 26 minutes. Time from alert to recovery: 4 minutes.

The postmortem

If you’ve done incident response at work, this format will look familiar. I use it at home too — not because it’s bureaucratic overhead, but because it forces honest thinking.

What failed: tsx missing from node_modules on LXC 101 (nx-web-01). Root cause unknown — most likely a stale node_modules state after a system update or partial npm ci run that didn’t complete cleanly.

What detected it: External monitoring (Prometheus + pm2-prometheus-exporter tracking restart counts per process). NOT the service itself.

What slowed detection: The 22-minute gap between first failure and alert. PM2’s default backoff delays mean a fast-dying service doesn’t trip alerts immediately. I had no alert threshold on restart rate — only on sustained high restart counts.

What enabled fast recovery: The service is stateless. No data to recover. npm install is idempotent. Recovery procedure existed in muscle memory.

What didn’t exist: A startup health check that validates dependencies before PM2 marks the service alive. The service was failing fast, not failing safe.

The fixes

1. Prestart dependency validation

Added to package.json:

"prestart": "npm ls --depth=0 --silent || (echo 'Dependency validation failed — run npm install' && exit 1)"

Now on restart, PM2 gets a clean exit with an actionable message. Restart 1 tells me what’s wrong. Not restart 32.

2. Alert on restart rate, not just count

Updated the Prometheus alert rule:

- alert: PM2ServiceRestartRateSpiking
  expr: rate(pm2_restarts_total[5m]) > 0.1
  for: 2m
  labels:
    severity: warning
  annotations:
    summary: "{{ $labels.name }} restarting frequently"

This fires within 2 minutes of a sustained restart loop instead of waiting for a count threshold.

3. Runbook entry

I added a one-page runbook to my Obsidian vault:

Service: sjvik-control-panel
Recovery: cd /root/projects/sjvik-control-panel && npm install && pm2 restart sjvik-control-panel
Verify: curl -s http://localhost:3456/health | jq .status
Escalate if: health endpoint returns non-200 after npm install

Runbooks feel like overkill for a homelab until 2am when you’re tired and need the answer fast.

Why homelab IR matters

The enterprise version of this incident would involve a runbook, a Slack war room, a timeline in PagerDuty, and a written postmortem shared with the team. The homelab version is: you fixing something alone at 1am with nobody watching.

But the thinking is the same.

  • Detection time matters. Alert on behavior, not just state.
  • Recovery time improves with runbooks. Write them while you still remember what you did.
  • Postmortems find gaps that didn’t feel like gaps until something broke.

I have 11 containers. At least a few of them will have incidents. Treating each one as a real IR exercise is how I actually get better at this — not just at homelab ops, but at the SOC analyst work I do professionally.

The next time I’m in an enterprise war room following an incident response process, I’ll have done it 15 times already on my own infrastructure.

Running a homelab security stack? My Proxmox Homelab guide covers the full setup — Proxmox cluster, PBS backups, Wazuh SIEM, and the PM2 service patterns that kept this recoverable in under 5 minutes.

Browser vs Desktop: Why Pixel Art Tools Are Moving to the Web

The pixel art tool landscape is shifting. Aseprite has been the gold standard for years — a desktop app that costs $20. But a new wave of browser-based pixel art tools is emerging. Here is why that matters.

The Desktop Tool Problem

Desktop tools have real advantages: performance, offline access, deep OS integration. But they also have friction:

  • Install takes time and disk space
  • Updates require downloads
  • Cross-device workflow is painful
  • License keys and activations

For a tool you use for 30-second tasks, this friction is significant.

What Browser Tools Enable

Pixalo is a browser-based pixel art editor. Open it in any tab, draw, export. No install. No signup. No license key.

The browser is eating desktop tool territory because:

  • GPU acceleration (WebGL/WebGPU) closes the performance gap
  • Browser sandboxing enables zero-friction sharing
  • URL-based tools are inherently cross-platform
  • Auto-updates without user action

The Real Competition

Browser tools are not competing with Aseprite on features. They are competing on ACCESS. A tool that opens instantly in any browser will get used for different tasks than one that requires a 30-second launch sequence.

The Tradeoffs

Browser tools sacrifice some performance and deep OS integration. For serious pixel art production, desktop tools still win. But for quick edits, reference sketches, and accessibility, browser tools win.

Pixalo is positioned for the browser-native workflow. Pre-release now — join the waiting list at pixalo.app.

The Pixel Art Color Palette Guide: How to Choose Colors That Work

Color is the hardest part of pixel art. Not because picking colors is hard, but because picking the RIGHT colors — the ones that work together — is a skill that takes years to develop.

This guide is about building that intuition systematically.

Why Color Palettes Matter in Pixel Art

In traditional art, you have infinite colors. In pixel art, you have constraints. Most pixel artists limit themselves to 4-16 colors per sprite. This constraint forces intentionality.

A good palette makes a sprite look professional. A bad palette makes even excellent linework look amateur.

The Light-Mid-Dark System

The most reliable palette structure: build from light to dark within ONE hue.

For a red element:

  • Highlight: light red
  • Midtone: pure red
  • Shadow: dark red

This gives you 3 steps. Add a fourth for extreme light or shadow, and you have a complete base palette.

The 3-Color Rule

Start every sprite with just 3 colors:

  • A light tone
  • A midtone
  • A dark tone

Draw the whole sprite in 3 colors. If it looks good, you are done. If something feels missing, add a 4th color.

Pixalo includes palette management tools to lock in your colors before drawing.

Complementary Colors

Pair colors from opposite sides of the color wheel:

  • Red + green
  • Blue + orange
  • Yellow + purple

Use sparingly — one complementary pair per sprite is usually enough.

The Saturation Rule

Low saturation for shadows, full saturation for midtones and highlights. This mimics how light works in the real world and gives your art depth.

Testing Your Palette

Once you have 4-8 colors:

  1. Draw a simple shape (sphere, cube)
  2. Check if it reads at 32×32 pixels
  3. If it works small, your palette is solid

Resources

Lospec has an incredible library of curated palettes from professional pixel artists. Study them. The Pixel Art Palette List is the best reference available.

Pixalo makes it easy to import and manage palettes while drawing. Free, no install needed — just open pixalo.app and start experimenting.

Making good/ proper line queries in c#?

Query ?:var customerOrdersQuery = (from c in db.Customers
join o in db.Orders on c.CustomerId equals o.CustomerId
select new
{
CustomerName = c.Name,
OrderDate = o.OrderDate
}).ToList();

Method:var customerOrdersMethod = db.Customers
.Join(
db.Orders, // 1. Table to join
c => c.CustomerId, // 2. Key from the first table (Customers)
o => o.CustomerId, // 3. Key from the second table (Orders)
(c, o) => new // 4. The data you want to select
{
CustomerName = c.Name,
OrderDate = o.OrderDate
}
).ToList();

And then to display : @model IEnumerable

@{
ViewData[“Title”] = “Full Order Report”;
}

<h2>Customer Order Report</h2>

<table>
    <thead>
        <tr>
            <th>Customer Name</th>
            <th>Product Purchased</th>
            <th>Date of Order</th>
        </tr>
    </thead>
    <tbody>
        @* Loop through the joined data stored in the ViewModels *@
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.CustomerName</td>
                <td>@item.ProductName</td>
                <td>@Html.DisplayFor(modelItem =&gt; item.OrderDate)</td>
            </tr>
        }
    </tbody>
</table>