<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vojta Biberle</title>
    <description>The latest articles on DEV Community by Vojta Biberle (@vojtabiberle).</description>
    <link>https://dev.to/vojtabiberle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F250993%2F59c218eb-0cd6-46c2-a94f-acfece57b63f.jpg</url>
      <title>DEV Community: Vojta Biberle</title>
      <link>https://dev.to/vojtabiberle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vojtabiberle"/>
    <language>en</language>
    <item>
      <title>How This Blog Works in 2026</title>
      <dc:creator>Vojta Biberle</dc:creator>
      <pubDate>Fri, 13 Feb 2026 05:08:30 +0000</pubDate>
      <link>https://dev.to/vojtabiberle/how-this-blog-works-in-2026-4hpj</link>
      <guid>https://dev.to/vojtabiberle/how-this-blog-works-in-2026-4hpj</guid>
      <description>&lt;p&gt;Back in 2019 I wrote about how this blog works. A lot has changed since then — automated deploys, dev.to sync, AI-generated tweets. Time for an update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still Grav
&lt;/h2&gt;

&lt;p&gt;The foundation hasn't changed. Grav CMS, flat-file, PHP 8.2, Markdown, no database. Plugin updates and security patches aren't something I think about — Grav just runs.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;blog&lt;/code&gt; theme extends &lt;code&gt;quark&lt;/code&gt; and adds custom styles — alternating white/gray section backgrounds, a tag cloud with CSS custom properties, article cards. Pure vanilla JS.&lt;/p&gt;

&lt;p&gt;The site is bilingual — Czech (&lt;code&gt;*.cs.md&lt;/code&gt;) and English (&lt;code&gt;*.en.md&lt;/code&gt;). Every article has both files side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy via Deployer + GitLab CI
&lt;/h2&gt;

&lt;p&gt;This hasn't fundamentally changed since 2019, just gotten more solid. Deployer 7.3 handles the actual deploy — symlink strategy, shared dirs for cache, automatic cleanup of old releases.&lt;/p&gt;

&lt;p&gt;GitLab CI orchestrates everything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Push to &lt;code&gt;master&lt;/code&gt;&lt;/strong&gt; triggers the pipeline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build image&lt;/strong&gt; — Docker image with PHP 8.2 and Deployer (only when &lt;code&gt;Dockerfile.ci&lt;/code&gt; changes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy&lt;/strong&gt; — &lt;code&gt;dep deploy&lt;/code&gt; over SSH to &lt;code&gt;biberle.cz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance&lt;/strong&gt; — dev.to sync and cache clearing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The SSH key is stored as a base64 CI variable, decoded at runtime. The entire deploy is fully automatic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic dev.to Sync
&lt;/h2&gt;

&lt;p&gt;English articles automatically get published to &lt;a href="https://dev.to"&gt;dev.to&lt;/a&gt;. The sync script scans all English articles, matches them against existing dev.to articles via &lt;code&gt;canonical_url&lt;/code&gt;, and creates or updates as needed. Stateless — the canonical URL serves as the key. Runs in CI after every deploy and also on a schedule (for future-dated articles that become publishable).&lt;/p&gt;

&lt;p&gt;The reverse works too — importing dev.to articles into the local blog, downloading cover images, and setting the &lt;code&gt;canonical_url&lt;/code&gt; back to the blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Generated Tweets
&lt;/h2&gt;

&lt;p&gt;This is the new addition. When a new article goes live, it automatically gets tweeted. But not with some generic "New blog post!" — the tweet text is generated by OpenAI (gpt-4o-mini) following my writing style guide.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;All English articles are scanned&lt;/li&gt;
&lt;li&gt;Already tweeted articles get skipped based on a tracked list&lt;/li&gt;
&lt;li&gt;For new articles, the title, excerpt, and tags are sent to OpenAI&lt;/li&gt;
&lt;li&gt;OpenAI generates a tweet (max 250 chars) matching my tone&lt;/li&gt;
&lt;li&gt;Tweet is posted via Twitter API v2 with OAuth 1.0a&lt;/li&gt;
&lt;li&gt;The article URL is recorded and committed back to git&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The writing style guide lives in a single file, so there's one source of truth for both the AI and me.&lt;/p&gt;

&lt;p&gt;Tweets only go out on weekdays at 9:00 AM (Europe/Prague), via a separate GitLab CI schedule with the &lt;code&gt;TWITTER_POST=true&lt;/code&gt; variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Hasn't Changed
&lt;/h2&gt;

&lt;p&gt;Some things just work and there's no reason to touch them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grav&lt;/strong&gt; is still perfect for a personal blog — fast, simple, minimal maintenance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown + Git&lt;/strong&gt; is the best workflow for writing technical articles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployer&lt;/strong&gt; does exactly what it should and nothing more&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Umami analytics instead of Google Analytics (the template placeholder is already there)&lt;/li&gt;
&lt;li&gt;Better cover images for articles&lt;/li&gt;
&lt;li&gt;Maybe add an RSS feed for Czech articles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But mostly — write more. Automation is great, but it's useless without articles.&lt;/p&gt;

</description>
      <category>dev</category>
      <category>automation</category>
      <category>grav</category>
    </item>
    <item>
      <title>WordPress Theme from Lovable HTML in One Day</title>
      <dc:creator>Vojta Biberle</dc:creator>
      <pubDate>Tue, 10 Feb 2026 20:11:11 +0000</pubDate>
      <link>https://dev.to/vojtabiberle/wordpress-theme-from-lovable-html-in-one-day-576p</link>
      <guid>https://dev.to/vojtabiberle/wordpress-theme-from-lovable-html-in-one-day-576p</guid>
      <description>&lt;p&gt;I took an HTML export from Lovable, opened Claude Code, and in one day had a complete WordPress theme -- 11 custom blocks, 4 custom post types, deployment pipeline. 83 files, over 7,300 lines of code. Without an AI agent? That would've taken a week at least.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Brief
&lt;/h1&gt;

&lt;p&gt;I needed a new website for the Czech ecological organization &lt;a href="https://stromyvkrajine.cz" rel="noopener noreferrer"&gt;Stromy v krajine&lt;/a&gt; (Trees in the Landscape). Their old site was outdated and the client wanted to manage content themselves -- projects, events, team members, partners, and news. WordPress was the obvious choice.&lt;/p&gt;

&lt;p&gt;I had &lt;a href="https://lovable.dev" rel="noopener noreferrer"&gt;Lovable&lt;/a&gt; generate the design -- an AI tool that creates a complete React SPA from a text description. The result looked great: responsive design, beautiful color palette (forest green + gold), Tailwind CSS, interactive components. But the client needed WordPress, not a React app.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 1: Analyzing the Lovable Export
&lt;/h1&gt;

&lt;p&gt;Lovable generated a React/Vite SPA with dozens of components: &lt;code&gt;HeroSection.tsx&lt;/code&gt;, &lt;code&gt;AboutSection.tsx&lt;/code&gt;, &lt;code&gt;AchievementsSection.tsx&lt;/code&gt;, pages for news, contact, photo gallery. The entire design system in Tailwind with HSL variables -- &lt;code&gt;primary: 142 70% 25%&lt;/code&gt; (forest green), &lt;code&gt;accent: 47 96% 53%&lt;/code&gt; (gold).&lt;/p&gt;

&lt;p&gt;First step was having Claude Code explore the entire React codebase -- every component, every design token, every breakpoint. It built a complete map of what needed to be converted.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: WordPress Theme in an Hour
&lt;/h1&gt;

&lt;p&gt;From the React code analysis, Claude Code generated the entire theme structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;11 custom blocks&lt;/strong&gt; -- Hero, Quote + About, Achievements, Contact Info, Contact Form, Photo Gallery, listings for Projects, Events, Team, and Partners&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 custom post types&lt;/strong&gt; with Czech labels -- Projects (status, location, tree count), Events (date and time), Team (social media), Partners (logo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind pipeline&lt;/strong&gt; via Vite -- identical design tokens as in Lovable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Nav Walker&lt;/strong&gt; for desktop and mobile navigation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vanilla JS&lt;/strong&gt; lightbox with arrow navigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;55 files, over 5,000 lines -- in a single commit.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 3: The Pivot -- ACF to Carbon Fields
&lt;/h1&gt;

&lt;p&gt;The original plan was ACF (Advanced Custom Fields) for blocks and metadata. But ACF Free lacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repeater&lt;/strong&gt; fields (item lists)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gallery&lt;/strong&gt; fields (photo gallery)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options&lt;/strong&gt; pages (footer settings)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ACF Pro costs $199+/year. Instead I switched to &lt;strong&gt;Carbon Fields&lt;/strong&gt; -- an open-source alternative with full functionality via Composer.&lt;/p&gt;

&lt;p&gt;The migration happened the same day. Claude Code rewrote all 11 blocks from ACF API to Carbon Fields API, swapped &lt;code&gt;get_field()&lt;/code&gt; for &lt;code&gt;carbon_get_the_post_meta()&lt;/code&gt;, and added Docker Compose for local development. Zero licensing costs, 100% open-source.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4: Polish
&lt;/h1&gt;

&lt;p&gt;What remained was fine-tuning details to match the Lovable design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Styling the Aktuality (blog) page to match the Lovable template&lt;/li&gt;
&lt;li&gt;Contact form with validation replacing the placeholder&lt;/li&gt;
&lt;li&gt;Editable footer via WordPress widget areas&lt;/li&gt;
&lt;li&gt;CTA button moved from hardcoded header to WordPress menu system&lt;/li&gt;
&lt;li&gt;Gallery with lightbox and arrow navigation&lt;/li&gt;
&lt;li&gt;Editor styling -- blocks in Gutenberg get labels and borders&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Step 5: Deployment
&lt;/h1&gt;

&lt;p&gt;Finally, a Deployer pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local build via Vite (Tailwind + JS)&lt;/li&gt;
&lt;li&gt;rsync to production server with release management&lt;/li&gt;
&lt;li&gt;Zero-downtime deploy via symlinks&lt;/li&gt;
&lt;li&gt;Content sync scripts (WP-CLI export/import + search-replace)&lt;/li&gt;
&lt;li&gt;Media sync via rsync to a shared uploads directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Timeline
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0h&lt;/td&gt;
&lt;td&gt;Lovable HTML export imported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1h&lt;/td&gt;
&lt;td&gt;Complete WordPress theme + ACF blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2h&lt;/td&gt;
&lt;td&gt;Pivot to Carbon Fields + Docker setup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-6h&lt;/td&gt;
&lt;td&gt;Styling, admin flexibility, interactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7-8h&lt;/td&gt;
&lt;td&gt;Deployment pipeline, documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;24 commits over 2 days. 83 files. 7,300+ lines of code.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why This Wouldn't Work Without Claude Code
&lt;/h1&gt;

&lt;p&gt;This was a project where an AI agent really shined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Codebase analysis&lt;/strong&gt; -- explored the entire React export and understood the design system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure generation&lt;/strong&gt; -- 55 theme files in a single step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rapid pivot&lt;/strong&gt; -- ACF to Carbon Fields migration in an hour instead of a day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repetitive patterns&lt;/strong&gt; -- controllers, template parts, CPT registration, block definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind conversion&lt;/strong&gt; -- 1:1 transfer of design tokens from React to WordPress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key decisions -- architecture, Carbon Fields vs ACF, deployment strategy -- required human judgment. But the actual execution? The AI agent sped that up maybe 5-10x.&lt;/p&gt;

&lt;p&gt;One person + Claude Code = a complete WordPress theme in a day. The future of development is here.&lt;/p&gt;

</description>
      <category>dev</category>
    </item>
    <item>
      <title>Building a Supabase Driver for Keboola Connection</title>
      <dc:creator>Vojta Biberle</dc:creator>
      <pubDate>Mon, 09 Feb 2026 09:09:08 +0000</pubDate>
      <link>https://dev.to/vojtabiberle/building-a-supabase-driver-for-keboola-connection-5h54</link>
      <guid>https://dev.to/vojtabiberle/building-a-supabase-driver-for-keboola-connection-5h54</guid>
      <description>&lt;p&gt;I spent the past weeks integrating Supabase as an external data source into Keboola Connection. The result is a complete driver with OAuth 2.0, automatic schema discovery, and Supabase Marketplace integration. I also hit some bugs that nearly drove me crazy -- here's the whole story.&lt;/p&gt;

&lt;h1&gt;
  
  
  Supabase + Keboola
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; is an open-source Firebase alternative built on PostgreSQL -- authentication, real-time subscriptions, storage, and most importantly a full-featured PostgreSQL database. &lt;a href="https://keboola.com" rel="noopener noreferrer"&gt;Keboola&lt;/a&gt; is a data pipeline platform for ETL processes over various data sources.&lt;/p&gt;

&lt;p&gt;The goal was simple: let users connect their Supabase project as an external data source in Keboola and have data automatically flow into their pipelines.&lt;/p&gt;

&lt;h1&gt;
  
  
  From CLI to Web Controller
&lt;/h1&gt;

&lt;p&gt;Originally I wanted a CLI command to register OAuth credentials. But testing a full OAuth flow from the terminal is painful -- you're copying URLs back and forth, handling redirects manually, no visual feedback at all.&lt;/p&gt;

&lt;p&gt;So I pivoted early: instead of CLI, I built a web-based test harness at &lt;code&gt;/supabase/connect&lt;/code&gt;. A simple form where you enter &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;, it generates the redirect URL, sends you to Supabase for authorization, and handles the callback. This turned out to be crucial -- the iterative debugging that followed would have been impossible from a terminal.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Stateless OAuth Puzzle
&lt;/h1&gt;

&lt;p&gt;The first real challenge came from Symfony's security architecture. Routes with &lt;code&gt;#[AsPublicAction]&lt;/code&gt; run on a &lt;strong&gt;stateless firewall&lt;/strong&gt; -- no session available. But OAuth flows typically need to store the PKCE verifier between the authorization request and the callback. Symfony threw: &lt;em&gt;"Session was used while the request was declared stateless."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So what do you do? Encode everything -- client credentials, PKCE verifier, redirect URI -- directly into the OAuth &lt;code&gt;state&lt;/code&gt; parameter, signed with HMAC-SHA256 using &lt;code&gt;kernel.secret&lt;/code&gt;. The callback decodes the state, verifies the signature, and extracts the PKCE verifier. No session, no database, HMAC prevents tampering, and the state parameter inherently protects against CSRF. Elegant.&lt;/p&gt;

&lt;h1&gt;
  
  
  OAuth API Quirks
&lt;/h1&gt;

&lt;p&gt;Then came two smaller hurdles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;approval_prompt&lt;/code&gt; mystery.&lt;/strong&gt; The League OAuth2 library adds &lt;code&gt;approval_prompt&lt;/code&gt; to authorization requests by default (a Google OAuth convention). Supabase rejected it: &lt;em&gt;"Unrecognized key(s) in object: 'approval_prompt'."&lt;/em&gt; Fix: override &lt;code&gt;getAuthorizationParameters()&lt;/code&gt; and filter it out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project vs. account OAuth apps.&lt;/strong&gt; I created the OAuth app at project level (&lt;code&gt;/project/{ref}/auth/oauth-apps&lt;/code&gt;) and kept getting &lt;em&gt;"Unrecognized client_id."&lt;/em&gt; Turns out Supabase has two distinct OAuth scopes -- project-specific apps and &lt;strong&gt;integration apps&lt;/strong&gt; at the account level (&lt;code&gt;/account/integrations&lt;/code&gt;). For a marketplace integration with cross-project access, you need the latter. Easy to miss in the docs.&lt;/p&gt;

&lt;h1&gt;
  
  
  The PKCE Double-Handling Bug
&lt;/h1&gt;

&lt;p&gt;This was the trickiest bug of the entire project. After solving the stateless flow and API quirks, token exchange kept failing: &lt;em&gt;"Invalid or expired OAuth authorization."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I tried everything systematically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detailed error logging -- same error&lt;/li&gt;
&lt;li&gt;HTTP Basic Auth for the token endpoint -- still nothing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Accept: application/json&lt;/code&gt; header -- still nothing&lt;/li&gt;
&lt;li&gt;A completely custom &lt;code&gt;exchangeCodeForTokens()&lt;/code&gt; bypassing the library -- still nothing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The authorization flow worked. The callback received the code. But token exchange kept returning the same cryptic error. I was going mad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The breakthrough:&lt;/strong&gt; the League OAuth2 library has built-in PKCE support. My code was &lt;em&gt;also&lt;/em&gt; handling PKCE manually for the stateless flow. Two correct PKCE implementations running simultaneously -- the library sent one &lt;code&gt;code_verifier&lt;/code&gt;, my stateless code sent a different one. Supabase saw a mismatch and rejected the exchange.&lt;/p&gt;

&lt;p&gt;The fix? One line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nv"&gt;$pkceMethod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Disable library's built-in PKCE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A classic integration bug -- two systems, each correct on its own, breaking when combined. Debugging took hours because every individual piece looked fine.&lt;/p&gt;

&lt;h1&gt;
  
  
  Two Connection Modes
&lt;/h1&gt;

&lt;p&gt;The driver supports two ways to access Supabase data:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Direct PostgreSQL connection&lt;/strong&gt; -- the classic approach via connection pooler (port 6543). Full SQL access, good for larger data volumes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;REST API (PostgREST)&lt;/strong&gt; -- via Supabase REST endpoint with a &lt;code&gt;service_role&lt;/code&gt; key. Simpler setup without exposing database passwords.&lt;/p&gt;

&lt;p&gt;Both modes support encrypted credential storage with separate encryption keys for passwords, API keys, and OAuth tokens.&lt;/p&gt;

&lt;h1&gt;
  
  
  Automatic Schema Discovery
&lt;/h1&gt;

&lt;p&gt;After a successful OAuth connection, a &lt;code&gt;SupabaseProjectSetupJob&lt;/code&gt; runs in the background:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Calls &lt;code&gt;ListSchemasCommand&lt;/code&gt; on the Supabase driver via protobuf&lt;/li&gt;
&lt;li&gt;Discovers available database schemas&lt;/li&gt;
&lt;li&gt;Creates an &lt;strong&gt;external bucket&lt;/strong&gt; in Keboola for each schema&lt;/li&gt;
&lt;li&gt;Sets up auto-refresh every 6 hours&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The protobuf communication is architecturally neat -- commands are defined in a shared &lt;code&gt;storage-driver-common&lt;/code&gt; monorepo and implemented in &lt;code&gt;storage-driver-postgres&lt;/code&gt;. Clean boundaries, no direct coupling.&lt;/p&gt;

&lt;p&gt;Users see their data in Keboola immediately after connecting, no manual configuration needed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Supabase Management API
&lt;/h1&gt;

&lt;p&gt;The integration includes a client for the Supabase Management API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project details (region, configuration)&lt;/li&gt;
&lt;li&gt;API keys (&lt;code&gt;anon&lt;/code&gt;, &lt;code&gt;service_role&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Connection pooler configuration&lt;/li&gt;
&lt;li&gt;List of projects and organizations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This data is used during automatic credential setup after the OAuth flow.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Full User Flow
&lt;/h1&gt;

&lt;p&gt;Here's what the end-to-end experience looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User clicks "Connect Supabase" and authorizes via OAuth&lt;/li&gt;
&lt;li&gt;Callback stores tokens, redirects to setup page&lt;/li&gt;
&lt;li&gt;User picks their Supabase project, optionally provides a PostgreSQL DSN&lt;/li&gt;
&lt;li&gt;Keboola creates an organization and project with a Supabase backend&lt;/li&gt;
&lt;li&gt;Background job discovers tables, registers buckets, sets up auto-refresh&lt;/li&gt;
&lt;li&gt;User lands on their project dashboard with data already flowing&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Scale
&lt;/h1&gt;

&lt;p&gt;The entire driver was built in &lt;strong&gt;one week&lt;/strong&gt;, roughly &lt;strong&gt;60 hours&lt;/strong&gt; of intensive work with &lt;a href="https://claude.ai/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;. The result is around &lt;strong&gt;25,000 changes&lt;/strong&gt; -- classes, controllers, migrations, tests, CLI commands, and documentation. From zero to a complete integration.&lt;/p&gt;

&lt;p&gt;The workflow was highly iterative: figure out what's needed, implement it, test immediately in the browser, debug from real error messages. Fast feedback loops made it possible to crack even the PKCE double-handling issue in hours rather than days.&lt;/p&gt;

&lt;p&gt;The AI agent massively accelerated repetitive patterns (controllers, DTOs, tests), boilerplate generation, and navigating the large Keboola codebase. But key architectural decisions and the security model obviously required human judgment.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateless OAuth works&lt;/strong&gt; -- encode everything in an HMAC-signed state parameter, no session needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch your libraries&lt;/strong&gt; -- the PKCE double-handling taught me to always check what the library does automatically behind your back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OAuth APIs differ wildly between providers&lt;/strong&gt; -- &lt;code&gt;approval_prompt&lt;/code&gt;, auth methods, app scopes, nothing is standard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PKCE is a must&lt;/strong&gt; for flows where the client secret can't be fully secured&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A web test harness&lt;/strong&gt; beats CLI testing for OAuth flows by an order of magnitude&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agents change the game&lt;/strong&gt; -- 25,000 changes in a week wouldn't have been possible without one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The integration is currently in pull request and going through code review. Looking forward to seeing it in production.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>php</category>
      <category>oauth</category>
      <category>webdev</category>
    </item>
    <item>
      <title>KDE Plasma Activities on stereoides</title>
      <dc:creator>Vojta Biberle</dc:creator>
      <pubDate>Fri, 05 Apr 2024 19:38:18 +0000</pubDate>
      <link>https://dev.to/vojtabiberle/kde-plasma-activities-on-stereoides-2mkm</link>
      <guid>https://dev.to/vojtabiberle/kde-plasma-activities-on-stereoides-2mkm</guid>
      <description>&lt;p&gt;I used to regard KDE Plasma Activities as merely pointless and dull—like fancy workspaces with added overhead. However, I couldn't have been more mistaken!&lt;/p&gt;

&lt;p&gt;Regrettably, this functionality isn't readily visible to the average user, which is a drawback for Activities.&lt;/p&gt;

&lt;p&gt;Activities support running scripts for four actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Activated: Executes when the activity is selected or focused.&lt;/li&gt;
&lt;li&gt;Deactivated: Triggers when switching to another activity.&lt;/li&gt;
&lt;li&gt;Started: Runs upon activity initiation.&lt;/li&gt;
&lt;li&gt;Stopped: Executes when the activity is halted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there's no graphical user interface (GUI) for this functionality. While there's a &lt;a href="https://discuss.kde.org/t/script-management-for-activities/3359" rel="noopener noreferrer"&gt;Brainstorming discussion&lt;/a&gt; in the KDE forum from June 2023, there doesn't seem to be any subsequent development on this front.&lt;/p&gt;

&lt;p&gt;So, let's set up scripts manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Scripts
&lt;/h2&gt;

&lt;p&gt;First, you need to identify the IDs of the activities you're running. You can do this with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kactivities-cli &lt;span class="nt"&gt;--list-activities&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command provides you with a table containing the Activity ID, Activity Name, and Activity Icon. You just need the &lt;code&gt;Activity ID&lt;/code&gt;. Next, navigate to the folder &lt;code&gt;.local/share/kactivitymanagerd/activities/&lt;/code&gt; (create it if any part of the path doesn't exist). Inside this folder, create a subfolder with the same name as the &lt;code&gt;Activity ID&lt;/code&gt; you'd like to script. Then, create subfolders for each action you want to script: &lt;code&gt;activated&lt;/code&gt;, &lt;code&gt;deactivated&lt;/code&gt;, &lt;code&gt;started&lt;/code&gt;, or &lt;code&gt;stopped&lt;/code&gt;. You can create folders for all of them if needed. Then, any script placed in one of these action folders will be executed accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enhanced Functionality
&lt;/h2&gt;

&lt;p&gt;With these scripts in place, you have fully automated activity switching in Plasma. What's next? For example, you can customize the default browser for each activity.&lt;/p&gt;

&lt;p&gt;For this, let's utilize the &lt;code&gt;utils.sh&lt;/code&gt; script, which you can place wherever you prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;writeconf&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  kwriteconfig5 &lt;span class="nt"&gt;--file&lt;/span&gt; ~/.config/kdeglobals &lt;span class="nt"&gt;--group&lt;/span&gt; General &lt;span class="nt"&gt;--key&lt;/span&gt; BrowserApplication &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  xdg-settings &lt;span class="nb"&gt;set &lt;/span&gt;default-web-browser &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  xdg-mime default &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; x-scheme-handler/https
  xdg-mime default &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; x-scheme-handler/http
  xdg-mime default &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; text/html
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;readconf&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  kreadconfig5 &lt;span class="nt"&gt;--file&lt;/span&gt; ~/.config/kdeglobals &lt;span class="nt"&gt;--group&lt;/span&gt; General &lt;span class="nt"&gt;--key&lt;/span&gt; BrowserApplication
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, create separate scripts for activating Chrome and Firefox:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;activate-chrome.sh&lt;/code&gt; - placed in &lt;code&gt;activated&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bin/activities/utils.sh

&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;org.kde.ActivityManager
&lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;.Activities
&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ActivityManager/Activities
&lt;span class="nv"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CurrentActivityChanged

&lt;span class="nv"&gt;curact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;qdbus &lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="nv"&gt;$interface&lt;/span&gt;.CurrentActivity&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;qdbus &lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="nv"&gt;$interface&lt;/span&gt;.ActivityName &lt;span class="nv"&gt;$curact&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Switched to activity &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Previous browser: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;readconf&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Setting browser to Chrome"&lt;/span&gt;
writeconf google-chrome.desktop
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current Browser: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;readconf&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;activate-firefox.sh&lt;/code&gt; - placed in &lt;code&gt;deactivated&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash  &lt;/span&gt;

&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bin/activities/utils.sh  

&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;org.kde.ActivityManager  
&lt;span class="nv"&gt;interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;.Activities  
&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ActivityManager/Activities  
&lt;span class="nv"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CurrentActivityChanged  

&lt;span class="nv"&gt;curact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;qdbus &lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="nv"&gt;$interface&lt;/span&gt;.CurrentActivity&lt;span class="si"&gt;)&lt;/span&gt;  
&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;qdbus &lt;span class="nv"&gt;$service&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="nv"&gt;$interface&lt;/span&gt;.ActivityName &lt;span class="nv"&gt;$curact&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Switched to activity &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Previous browser: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;readconf&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Setting browser to Firefox"&lt;/span&gt;  
writeconf firefox.desktop  
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Current Browser: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;readconf&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine these scripts being applied to your &lt;code&gt;Work&lt;/code&gt; activity. When you switch to &lt;code&gt;Personal&lt;/code&gt;, your default browser switches back to &lt;code&gt;Firefox&lt;/code&gt;. This setup gives you tailored browser switching for different activities in Plasma.&lt;/p&gt;

&lt;p&gt;If you prefer browsers other than Firefox or Chrome, you can easily modify the scripts accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Tips
&lt;/h2&gt;

&lt;p&gt;I often find it useful to have certain applications pinned to the taskbar across all activities. By default, applications are only shown in the activity where they were launched. However, you can create a KWin window rule with the condition "All activities" to ensure these applications are always accessible.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Conclusion
&lt;/h2&gt;

&lt;p&gt;This setup has been a significant game-changer for me. As someone who heavily uses Slack for both work and personal communication, having automated browser switching based on activities has streamlined my workflow immensely.&lt;/p&gt;

&lt;p&gt;Next, I plan to further customize my Slack experience by separating work-related Slack workspaces from others, allowing for even greater efficiency and organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Switching Alternatives
&lt;/h2&gt;

&lt;p&gt;If you're interested in alternative methods for activity switching and scripting, you might want to explore &lt;a href="https://browsers.software/" rel="noopener noreferrer"&gt;Browsers&lt;/a&gt;, an intuitive context menu that appears when you click a link in an app other than a web browser.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>kde</category>
    </item>
    <item>
      <title>Taskfile - really simple task runner</title>
      <dc:creator>Vojta Biberle</dc:creator>
      <pubDate>Mon, 06 Jan 2020 21:42:17 +0000</pubDate>
      <link>https://dev.to/vojtabiberle/taskfile-really-simple-taks-runner-986</link>
      <guid>https://dev.to/vojtabiberle/taskfile-really-simple-taks-runner-986</guid>
      <description>&lt;p&gt;Not so long ago I discovered the &lt;a href="https://github.com/adriancooney/Taskfile" rel="noopener noreferrer"&gt;&lt;code&gt;Taskfile&lt;/code&gt;&lt;/a&gt; runner from &lt;a href="https://github.com/adriancooney" rel="noopener noreferrer"&gt;Adrian Cooney&lt;/a&gt;. Adrian hasn't worked on the repository for about 3 years, so I decided to improve it a little bit for my own usage.&lt;/p&gt;

&lt;p&gt;There isn't much to fork and I didn't want to open a pull request, so I just copied everything I found useful, prepared one more template (there will be even more templates soon) and published the repository.&lt;/p&gt;

&lt;p&gt;I'll be happy if anybody finds it useful. Here is my version of &lt;a href="https://github.com/vojtabiberle/Taskfile" rel="noopener noreferrer"&gt;&lt;code&gt;Taskfile&lt;/code&gt;&lt;/a&gt;. Enjoy!&lt;/p&gt;

</description>
      <category>taskrunner</category>
      <category>bash</category>
      <category>linux</category>
    </item>
    <item>
      <title>I discovered a new WordPress local dev-env</title>
      <dc:creator>Vojta Biberle</dc:creator>
      <pubDate>Wed, 11 Dec 2019 01:12:41 +0000</pubDate>
      <link>https://dev.to/vojtabiberle/i-discovered-new-wordpress-local-dev-env-5fd0</link>
      <guid>https://dev.to/vojtabiberle/i-discovered-new-wordpress-local-dev-env-5fd0</guid>
      <description>&lt;h1&gt;
  
  
  Opening
&lt;/h1&gt;

&lt;p&gt;From time to time I have to build WordPress pages. WordPress is not bad. It's actually good, but I use Docker for development and the official docker images are not great.&lt;/p&gt;

&lt;p&gt;I actually managed my own PHP WordPress images about 2 years ago, but they are now deprecated (PHP 7.0 and PHP 7.1) and I don't have time or strength to manage them again.&lt;/p&gt;

&lt;p&gt;Let's search for some ready-made solution!&lt;/p&gt;

&lt;h1&gt;
  
  
  Wodby
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5m2f87xn97fpf83djvk5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5m2f87xn97fpf83djvk5.png" alt="Wodby logo" width="100" height="100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Managed stacks and infrastructure for post-container world -- that's their marketing claim.&lt;/p&gt;

&lt;p&gt;And not only that. These guys provide us with local development stacks with everything I missed from the official WordPress image.&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UID/GID out of the box&lt;/li&gt;
&lt;li&gt;xDebug&lt;/li&gt;
&lt;li&gt;tooling for wp-cli&lt;/li&gt;
&lt;li&gt;tooling for composer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And much more. Just check their guide for WordPress: &lt;a href="https://wodby.com/docs/stacks/wordpress/local/" rel="noopener noreferrer"&gt;https://wodby.com/docs/stacks/wordpress/local/&lt;/a&gt; -- it's very easy to follow and start developing in WordPress.&lt;/p&gt;

&lt;p&gt;Are you interested in Drupal, PHP, Ruby or Python? &lt;a href="https://wodby.com/docs/stacks/" rel="noopener noreferrer"&gt;They have them too.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And bonus? You can use their deployment stack as a developer for free! Great for showing your work to your clients ;-)&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;The installation process is pretty clear, here are just a few adjustments I made.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do not clone/fork &lt;a href="https://github.com/wodby/wordpress-composer" rel="noopener noreferrer"&gt;wodby/wordpress-composer&lt;/a&gt; but just run &lt;code&gt;composer create-project wodby/wordpress-composer some-dir --stability dev --no-interaction&lt;/code&gt; as written in the repository README.md&lt;/li&gt;
&lt;li&gt;I changed &lt;code&gt;$PROJECT_BASE_URL&lt;/code&gt; to just &lt;code&gt;$PROJECT_NAME.local&lt;/code&gt; -- this is shorter :-)&lt;/li&gt;
&lt;li&gt;I uncommented mounted volumes for MariaDB, because persistent data are better (you can mount it with just &lt;code&gt;- ./database:/var/lib/mysql&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;If you have an issue with MariaDB not starting, just try another MariaDB tag in your &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You don't need to manually change &lt;code&gt;wp-config.php&lt;/code&gt;. Just point your browser to WordPress and let the installer do everything for you&lt;/li&gt;
&lt;li&gt;Uncomment the &lt;code&gt;Adminer&lt;/code&gt; section in &lt;code&gt;docker-compose.yml&lt;/code&gt; -- just my personal preference&lt;/li&gt;
&lt;li&gt;Uncomment &lt;code&gt;NGINX_SERVER_ROOT&lt;/code&gt; for Nginx and add &lt;code&gt;/web&lt;/code&gt; to the end&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;make up&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Profit&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;This took me more time than I expected for the first run. But now I'm very happy with the dev environment. I have prepared my own template for future projects and I hope I will only need to update dependencies going forward.&lt;/p&gt;

&lt;p&gt;Thanks, guys from Wodby!&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>docker</category>
      <category>wodby</category>
    </item>
  </channel>
</rss>
