<?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: Adela</title>
    <description>The latest articles on DEV Community by Adela (@adela_bytebase).</description>
    <link>https://dev.to/adela_bytebase</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F770925%2F7fb08e1e-f7be-475e-9465-811787b28842.jpg</url>
      <title>DEV Community: Adela</title>
      <link>https://dev.to/adela_bytebase</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adela_bytebase"/>
    <language>en</language>
    <item>
      <title>From Zero to First Migration: How Much Setup Do Flyway, Liquibase, and Bytebase Require?</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Mon, 24 Aug 2026 09:46:59 +0000</pubDate>
      <link>https://dev.to/adela_bytebase/from-zero-to-first-migration-how-much-setup-do-flyway-liquibase-and-bytebase-require-1p6g</link>
      <guid>https://dev.to/adela_bytebase/from-zero-to-first-migration-how-much-setup-do-flyway-liquibase-and-bytebase-require-1p6g</guid>
      <description>&lt;p&gt;Starting with a fresh PostgreSQL database, what does it actually take a beginner to complete their first managed schema migration?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short answer: Flyway Desktop and Bytebase Cloud got there in about half an hour each; Liquibase Community CLI took about an hour and a half, not because of its migration engine but because its beginner guide sent me to the wrong Java version and skipped a required PostgreSQL driver.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this experiment I started as a new user with Flyway, Liquibase, and Bytebase, applied the same SQL change to three fresh databases hosted on Neon, and recorded what happened along the way.&lt;/p&gt;

&lt;p&gt;One methodology choice shapes everything below: &lt;strong&gt;I let each vendor pick the on-ramp.&lt;/strong&gt; Rather than forcing the three products into the same shape, I took whatever path their own beginner documentation recommends.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is &lt;strong&gt;Flyway Desktop&lt;/strong&gt; rather than the Flyway CLI — Redgate's Get Started guide steers new users to the desktop app.&lt;/li&gt;
&lt;li&gt;This is &lt;strong&gt;Bytebase Cloud&lt;/strong&gt; rather than self-hosted Docker — the Bytebase quickstart presents Cloud first, as the path with no installation.&lt;/li&gt;
&lt;li&gt;Liquibase is the &lt;strong&gt;Community CLI&lt;/strong&gt;, the easiest entry point its documentation offers.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Flyway Desktop&lt;/th&gt;
&lt;th&gt;Liquibase Community CLI&lt;/th&gt;
&lt;th&gt;Bytebase Cloud&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vendor's recommended on-ramp&lt;/td&gt;
&lt;td&gt;Desktop app&lt;/td&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;Cloud, no install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Installed locally&lt;/td&gt;
&lt;td&gt;Desktop app, 663 MB&lt;/td&gt;
&lt;td&gt;JDK 17+, CLI, JDBC driver&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account required&lt;/td&gt;
&lt;td&gt;Redgate ID + email confirmation&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;Bytebase Cloud sign-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times the guide did not match reality&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Times I left the guide to find an answer&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rough time to first migration&lt;/td&gt;
&lt;td&gt;~30 minutes&lt;/td&gt;
&lt;td&gt;~1.5 hours&lt;/td&gt;
&lt;td&gt;~30 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;A disclosure: I work at Bytebase, so it's the one product here I already knew going in — for Flyway and Liquibase I was starting from zero. I controlled for that by following each vendor's own quickstart to the letter rather than improvising. The times above are approximate, not stopwatch measurements — read them as orders of magnitude, not a benchmark; the countable rows are the more reliable comparison.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Official guides followed
&lt;/h2&gt;

&lt;p&gt;Rather than design an idealized workflow for each product, I started from the official beginner documentation available at the time of the test and adapted only the connection details and the SQL, so all three runs used Neon PostgreSQL and the same schema change.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Official getting-started documentation followed&lt;/th&gt;
&lt;th&gt;How it was adapted for this test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flyway Desktop&lt;/td&gt;
&lt;td&gt;&lt;a href="https://documentation.red-gate.com/fd/quickstart-flyway-desktop-206602598.html" rel="noopener noreferrer"&gt;Quickstart — Flyway Desktop&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;The guide's sample database and SQL were replaced with the Neon &lt;code&gt;flyway_demo&lt;/code&gt; database and the shared &lt;code&gt;CREATE TABLE users&lt;/code&gt; migration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Liquibase Community CLI&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.liquibase.com/community/get-started-5-0/get-started-install" rel="noopener noreferrer"&gt;Install Liquibase&lt;/a&gt; and &lt;a href="https://docs.liquibase.com/community/implementation-guide-5-0/intro-to-liquibase" rel="noopener noreferrer"&gt;Introduction to Liquibase&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;I followed the installation and introductory project flow, then connected the generated project to the Neon &lt;code&gt;liquibase_demo&lt;/code&gt; database and used a formatted SQL changeset.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytebase Cloud&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.bytebase.com/get-started/step-by-step/start-bytebase" rel="noopener noreferrer"&gt;Step 1: Start Bytebase&lt;/a&gt;, which opens the step-by-step quickstart by offering Bytebase Cloud and self-hosted Docker side by side&lt;/td&gt;
&lt;td&gt;I took the Cloud path the page presents first, connected the Neon instance, and applied the shared SQL to &lt;code&gt;bytebase_demo&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Worth flagging: Flyway also ships a free CLI that needs no Redgate account, and Bytebase also self-hosts with Docker, which would have added deployment time. I followed each vendor's own beginner path either way.&lt;/p&gt;

&lt;p&gt;Liquibase also sent me to two supplemental pages: &lt;a href="https://docs.liquibase.com/community/integration-guide-5-0/connect-liquibase-with-postgresql" rel="noopener noreferrer"&gt;Connect Liquibase with PostgreSQL&lt;/a&gt; when the driver turned out to be missing, and the &lt;a href="https://docs.liquibase.com/community/get-started-5-0-3/system-requirements" rel="noopener noreferrer"&gt;5.0.3 system requirements&lt;/a&gt; when the Java runtime became an issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Each tool got its own empty Neon PostgreSQL database — &lt;code&gt;flyway_demo&lt;/code&gt;, &lt;code&gt;liquibase_demo&lt;/code&gt;, and &lt;code&gt;bytebase_demo&lt;/code&gt; — with the host, port, credentials, and database name saved in advance. Neon requires SSL, so every connection had to be configured for it: a small tax paid equally by all three, and one a local PostgreSQL instance would not have charged.&lt;/p&gt;

&lt;p&gt;Each database then received exactly the same migration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The finish line is the same too: the migration succeeds, the &lt;code&gt;users&lt;/code&gt; table exists, and the tool shows a record of the applied change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flyway Desktop: install, connect, migrate
&lt;/h2&gt;

&lt;p&gt;I followed Redgate's &lt;a href="https://documentation.red-gate.com/fd/quickstart-flyway-desktop-206602598.html" rel="noopener noreferrer"&gt;Quickstart — Flyway Desktop&lt;/a&gt;, replacing its sample database and SQL with Neon and my shared migration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing and creating the project
&lt;/h3&gt;

&lt;p&gt;I downloaded Flyway Desktop — 663 MB for the macOS Arm64 build, the single largest download in this experiment — and installed it. On first launch, the application asked me to sign in with a Redgate ID. Because I was starting from zero, this also meant registering an account, confirming my email address, signing in again, and choosing between Community and an Enterprise trial. I selected &lt;strong&gt;Flyway Community — free to use&lt;/strong&gt;.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhp6n241sxsa30rppsl98.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhp6n241sxsa30rppsl98.png" alt="Redgate asking me to confirm my email address" width="800" height="462"&gt;&lt;/a&gt;Redgate asking me to confirm my email address&lt;br&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtawl3lp82nb3qrth78r.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxtawl3lp82nb3qrth78r.png" alt="Flyway Desktop asking me to choose between Community and an Enterprise trial" width="800" height="457"&gt;&lt;/a&gt;Flyway Desktop asking me to choose between Community and an Enterprise trial&lt;/p&gt;

&lt;p&gt;After signing in, I landed on the project screen. I selected &lt;strong&gt;New project&lt;/strong&gt;, chose PostgreSQL as the database type, and created a local &lt;code&gt;flyway-demo&lt;/code&gt; project.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3uswpjuwexl6tu2y8474.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3uswpjuwexl6tu2y8474.png" alt="Flyway Desktop new-project screen" width="799" height="250"&gt;&lt;/a&gt;Flyway Desktop new-project screen&lt;/p&gt;
&lt;h3&gt;
  
  
  Connecting Neon
&lt;/h3&gt;

&lt;p&gt;I added the Neon PostgreSQL database as the target environment, entered the connection details for &lt;code&gt;flyway_demo&lt;/code&gt;, and tested the connection.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5vqzcqki8153bee9bs1n.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5vqzcqki8153bee9bs1n.png" alt="Flyway Desktop successfully connecting to the Neon target database, with credentials redacted" width="800" height="457"&gt;&lt;/a&gt;Flyway Desktop successfully connecting to the Neon target database, with credentials redacted&lt;/p&gt;
&lt;h3&gt;
  
  
  Adding the migration
&lt;/h3&gt;

&lt;p&gt;I created a versioned migration and pasted in the shared SQL. Flyway Desktop generated a timestamp-based version, and I used &lt;code&gt;create_table_users&lt;/code&gt; as the description.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjy23k0kzum2kd1ezl1aa.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjy23k0kzum2kd1ezl1aa.png" alt="Creating the versioned migration in Flyway Desktop" width="799" height="454"&gt;&lt;/a&gt;Creating the versioned migration in Flyway Desktop&lt;/p&gt;

&lt;p&gt;The version prefix gives Flyway the order in which migrations should run, while the description makes the change recognizable in history.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running Migrate
&lt;/h3&gt;

&lt;p&gt;After I saved the file, Flyway showed one pending migration against the &lt;code&gt;Neon Flyway Demo&lt;/code&gt; target.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxs50tyjs2vj9hi3jnv60.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxs50tyjs2vj9hi3jnv60.png" alt="The new Flyway migration in Pending state" width="800" height="452"&gt;&lt;/a&gt;The new Flyway migration in Pending state&lt;/p&gt;

&lt;p&gt;I selected &lt;strong&gt;Migrate&lt;/strong&gt; and clicked &lt;strong&gt;Run migrate&lt;/strong&gt;. Flyway applied the migration and its state moved from &lt;strong&gt;Pending&lt;/strong&gt; to &lt;strong&gt;Success&lt;/strong&gt; (466 ms recorded for the SQL itself).&lt;/p&gt;
&lt;h3&gt;
  
  
  What I saw at the finish line
&lt;/h3&gt;

&lt;p&gt;The migration was marked successful and appeared in the migration history, with a managed record of the timestamp-based version being applied.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqm5hzsfxqc7diqrd4map.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqm5hzsfxqc7diqrd4map.png" alt="Flyway Desktop showing the migration as successful" width="800" height="451"&gt;&lt;/a&gt;Flyway Desktop showing the migration as successful&lt;/p&gt;

&lt;p&gt;I then opened Neon to verify the database state independently. The &lt;code&gt;users&lt;/code&gt; table was present, and &lt;code&gt;flyway_schema_history&lt;/code&gt; contained the versioned migration with the description &lt;code&gt;create table users&lt;/code&gt;.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvn2u1agw626sh3jtxft.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frvn2u1agw626sh3jtxft.png" alt="Neon showing the users table and Flyway schema history" width="799" height="410"&gt;&lt;/a&gt;Neon showing the users table and Flyway schema history&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;flyway_schema_history&lt;/code&gt; table stores the migration version, description, script, checksum, installation time, execution time, and success status.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frslsbeu7tdkm1tz5z4hp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frslsbeu7tdkm1tz5z4hp.png" alt="The structure of the Flyway schema history table in Neon" width="799" height="407"&gt;&lt;/a&gt;The structure of the Flyway schema history table in Neon&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rough time from download to visible history: about 30 minutes.&lt;/strong&gt; Nothing in the guide contradicted what I saw, and I never had to leave it to find an answer. Redgate's quickstart estimates five minutes, and the configure-and-migrate steps it actually covers are about that fast. The rest of my half hour went to the 663 MB download, the account registration, and the email confirmation — none of which the estimate counts.&lt;/p&gt;
&lt;h3&gt;
  
  
  Beginner takeaway
&lt;/h3&gt;

&lt;p&gt;Flyway Desktop keeps the main workflow visual: project, database target, migration file, and execution history. The user still works with a conventional versioned SQL file, but the desktop interface makes the target and result easy to inspect.&lt;/p&gt;
&lt;h2&gt;
  
  
  Liquibase Community CLI: three detours before the first update
&lt;/h2&gt;

&lt;p&gt;I started with Liquibase's &lt;a href="https://docs.liquibase.com/community/get-started-5-0/get-started-install" rel="noopener noreferrer"&gt;Install Liquibase&lt;/a&gt; and &lt;a href="https://docs.liquibase.com/community/implementation-guide-5-0/intro-to-liquibase" rel="noopener noreferrer"&gt;Introduction to Liquibase&lt;/a&gt; guides. On paper the path looked straightforward: install Liquibase, initialize a project, configure the connection, run the first update. My 5.0.3 run was not that linear — it became the most troubleshooting-heavy of the three.&lt;/p&gt;

&lt;p&gt;The documentation summarizes the standard workflow as changesets inside a changelog, deployed with &lt;code&gt;update&lt;/code&gt;, with &lt;code&gt;DATABASECHANGELOG&lt;/code&gt; and &lt;code&gt;DATABASECHANGELOGLOCK&lt;/code&gt; tracking the result. Here is the sequence I actually experienced:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Point in the run&lt;/th&gt;
&lt;th&gt;What I expected&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;Java setup&lt;/td&gt;
&lt;td&gt;Follow the Java link and continue&lt;/td&gt;
&lt;td&gt;The getting-started path led to Java 8, but Liquibase 5.0.3 requires Java 17 or later — so I installed a JDK twice.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selecting the runtime&lt;/td&gt;
&lt;td&gt;The newly installed JDK 17 would be used automatically&lt;/td&gt;
&lt;td&gt;My shell still used the older Java installation, so I had to set &lt;code&gt;JAVA_HOME&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project initialization&lt;/td&gt;
&lt;td&gt;The generated files would match the documented example&lt;/td&gt;
&lt;td&gt;My 5.0.3 run produced only &lt;code&gt;example-changelog.sql&lt;/code&gt; and &lt;code&gt;liquibase.properties&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First PostgreSQL connection&lt;/td&gt;
&lt;td&gt;Liquibase would connect after I supplied the JDBC URL and credentials&lt;/td&gt;
&lt;td&gt;The PostgreSQL driver was missing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Driver installation&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;I found the PostgreSQL-specific guide and ran &lt;code&gt;liquibase lpm add postgresql&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;liquibase update&lt;/code&gt; would apply the changeset&lt;/td&gt;
&lt;td&gt;After resolving the earlier issues, the update succeeded.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  First obstacle: which Java version did I need?
&lt;/h3&gt;

&lt;p&gt;The getting-started path pointed me to a Java 8 download — &lt;code&gt;jre-8u501-macosx-aarch64.dmg&lt;/code&gt;, 93.2 MB. After installing it, I found it was not compatible with the Liquibase version I was running: the &lt;a href="https://docs.liquibase.com/community/get-started-5-0-3/system-requirements" rel="noopener noreferrer"&gt;5.0.3 system requirements&lt;/a&gt; put the minimum at Java 17.&lt;/p&gt;

&lt;p&gt;So the first Java install was wasted. I went back and downloaded a second JDK, this time 17. Even after installing it, &lt;code&gt;java -version&lt;/code&gt; still reported the old runtime — my shell had not picked up the new installation. I had to point &lt;code&gt;JAVA_HOME&lt;/code&gt; at the JDK 17 location and put its &lt;code&gt;bin&lt;/code&gt; directory first on &lt;code&gt;PATH&lt;/code&gt; — on macOS:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;/usr/libexec/java_home &lt;span class="nt"&gt;-v&lt;/span&gt; 17&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, Liquibase could finally start on the expected runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second surprise: the generated project looked different
&lt;/h3&gt;

&lt;p&gt;I downloaded the Liquibase archive — &lt;code&gt;liquibase-5.0.3.tar.gz&lt;/code&gt;, 8.3 MB — installed the Community CLI, and ran the project initialization flow from the introduction guide.&lt;/p&gt;

&lt;p&gt;Initialization produced only two relevant files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example-changelog.sql
liquibase.properties
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a smaller project structure than the documentation showed, and I initially wondered whether initialization had failed. It had not: one file holds the SQL changelog, the other the connection and changelog configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring the Neon connection
&lt;/h3&gt;

&lt;p&gt;I updated &lt;code&gt;liquibase.properties&lt;/code&gt; with the changelog path and the JDBC connection for &lt;code&gt;liquibase_demo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;changelog-file&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;example-changelog.sql&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;jdbc:postgresql://YOUR_NEON_HOST/liquibase_demo?sslmode=require&lt;/span&gt;
&lt;span class="py"&gt;username&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;YOUR_NEON_USER&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;YOUR_NEON_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Third obstacle: the PostgreSQL driver was missing
&lt;/h3&gt;

&lt;p&gt;My first connection attempt failed because Liquibase could not find the PostgreSQL JDBC driver. I left the introductory guide and found the database-specific &lt;a href="https://docs.liquibase.com/community/integration-guide-5-0/connect-liquibase-with-postgresql" rel="noopener noreferrer"&gt;Connect Liquibase with PostgreSQL&lt;/a&gt; page, which adds the driver through Liquibase Package Manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;liquibase lpm add postgresql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gave Liquibase what it needed to reach Neon — installing Liquibase itself had not installed what it takes to talk to PostgreSQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the SQL changeset
&lt;/h3&gt;

&lt;p&gt;I replaced the example content in &lt;code&gt;example-changelog.sql&lt;/code&gt; with a formatted SQL changeset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;--liquibase formatted sql&lt;/span&gt;

&lt;span class="c1"&gt;--changeset demo:1&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--changeset demo:1&lt;/code&gt; line gives the change an author and identifier. Liquibase uses that identity, together with the changelog file path, to determine whether the changeset has already run.&lt;/p&gt;

&lt;h3&gt;
  
  
  The update finally succeeded
&lt;/h3&gt;

&lt;p&gt;I checked the current state, inspected the SQL Liquibase planned to run, then applied it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;liquibase status
liquibase update-sql
liquibase update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time it worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I saw at the finish line
&lt;/h3&gt;

&lt;p&gt;In Neon I could see all three tables together: &lt;code&gt;users&lt;/code&gt;, plus the two management tables Liquibase creates, &lt;code&gt;databasechangelog&lt;/code&gt; and &lt;code&gt;databasechangeloglock&lt;/code&gt;. Inspecting &lt;code&gt;databasechangelog&lt;/code&gt; showed the applied changeset and the fields Liquibase uses to track it — &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;filename&lt;/code&gt;, &lt;code&gt;dateexecuted&lt;/code&gt;, &lt;code&gt;orderexecuted&lt;/code&gt;, and &lt;code&gt;md5sum&lt;/code&gt;.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7v65m36kck294lmyqd2s.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7v65m36kck294lmyqd2s.png" alt="Neon showing the Liquibase tracking tables and the DATABASECHANGELOG structure" width="800" height="408"&gt;&lt;/a&gt;Neon showing the Liquibase tracking tables and the DATABASECHANGELOG structure&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rough time from download to confirmed changeset: about an hour and a half.&lt;/strong&gt; Almost none of that was spent on migration work. The &lt;code&gt;update&lt;/code&gt; command itself was instant; the time went into the Java version mismatch, the &lt;code&gt;JAVA_HOME&lt;/code&gt; switch, the unexpected project layout, and the missing PostgreSQL driver.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beginner takeaway
&lt;/h3&gt;

&lt;p&gt;Liquibase exposes the moving parts clearly: the Java runtime, CLI, project files, JDBC driver, connection properties, and changeset metadata are all visible. But the beginner path did not prepare me for any of it — the guide sent me to Java 8 for a release that needs Java 17, and installing Liquibase did not install what it needed to talk to PostgreSQL. That's an onboarding problem, not an engine problem — though a new user has no way to tell the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bytebase Cloud: sign in, connect, plan, deploy
&lt;/h2&gt;

&lt;p&gt;I followed Bytebase's &lt;a href="https://docs.bytebase.com/get-started/step-by-step/start-bytebase" rel="noopener noreferrer"&gt;step-by-step quickstart&lt;/a&gt;, whose first page offers Bytebase Cloud and self-hosted Docker side by side, and took the Cloud path it presents first. Because Neon was publicly reachable, I did not need to install Bytebase locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Signing in and connecting Neon
&lt;/h3&gt;

&lt;p&gt;I signed in to Bytebase Cloud and created a workspace, then added a PostgreSQL instance using the Neon connection details, enabled SSL, and tested and saved the connection.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ny2bm3bh00kpp0ltujt.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ny2bm3bh00kpp0ltujt.png" alt="Connecting Bytebase Cloud to Neon and selecting bytebase_demo, with credentials redacted" width="799" height="450"&gt;&lt;/a&gt;Connecting Bytebase Cloud to Neon and selecting bytebase_demo, with credentials redacted&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a project and transferring the database
&lt;/h3&gt;

&lt;p&gt;I created a project, found &lt;code&gt;bytebase_demo&lt;/code&gt; under the connected instance, and transferred it in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the plan
&lt;/h3&gt;

&lt;p&gt;I created a new database-change plan, selected &lt;code&gt;bytebase_demo&lt;/code&gt;, and entered the same SQL used for the other tools.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz8lb3xvxerdx673ath2n.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz8lb3xvxerdx673ath2n.png" alt="Selecting bytebase_demo as the target of the new plan" width="799" height="407"&gt;&lt;/a&gt;Selecting bytebase_demo as the target of the new plan&lt;/p&gt;

&lt;p&gt;On save, Bytebase showed the target, the SQL statement, and the results of its built-in checks on the same plan page. Both checks passed, and the plan became ready for review.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr313bl4jgv625bcjlazp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr313bl4jgv625bcjlazp.png" alt="The Bytebase plan containing the SQL migration and two successful checks" width="800" height="380"&gt;&lt;/a&gt;The Bytebase plan containing the SQL migration and two successful checks&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing and deploying
&lt;/h3&gt;

&lt;p&gt;In my fresh workspace I had not configured a custom approval flow, so there was no manual approval step: the plan showed &lt;strong&gt;Review: Skipped&lt;/strong&gt;, followed by the deployment task for &lt;code&gt;bytebase_demo&lt;/code&gt;. I selected &lt;strong&gt;Run immediately&lt;/strong&gt;, and it finished successfully.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0ilfx0ts22rulh882pq.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0ilfx0ts22rulh882pq.png" alt="Running the Bytebase deployment task immediately after review was skipped" width="800" height="407"&gt;&lt;/a&gt;Running the Bytebase deployment task immediately after review was skipped&lt;/p&gt;

&lt;h3&gt;
  
  
  What I saw at the finish line
&lt;/h3&gt;

&lt;p&gt;I opened the database Changelog and found the recorded schema change. The &lt;code&gt;users&lt;/code&gt; table existed in &lt;code&gt;bytebase_demo&lt;/code&gt;, with the execution linked to the plan that introduced it.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxajbib49xnqbovueqfum.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxajbib49xnqbovueqfum.png" alt="The create table users entry in the Bytebase Changelog" width="800" height="381"&gt;&lt;/a&gt;The create table users entry in the Bytebase Changelog&lt;/p&gt;

&lt;p&gt;I also verified the result directly in Neon, where &lt;code&gt;bytebase_demo&lt;/code&gt; now contained the &lt;code&gt;users&lt;/code&gt; table.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnxvnhe0re0zojswa8cnt.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnxvnhe0re0zojswa8cnt.png" alt="The users table in the Neon bytebase_demo database" width="800" height="407"&gt;&lt;/a&gt;The users table in the Neon bytebase_demo database&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rough time from sign-in to visible changelog entry: about 30 minutes&lt;/strong&gt; — effectively the same as Flyway Desktop. Skipping the install saved time, but the workspace, instance, project, and database-transfer steps spent it again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beginner takeaway
&lt;/h3&gt;

&lt;p&gt;Bytebase Cloud skips local installation and presents the migration as a managed change request rather than a file plus a command. That means more vocabulary up front — projects, plans, review, deployment — in exchange for a review and deployment path that already exists by the time you need it. In a fresh workspace the review stage stays lightweight; the same structure supports stricter controls later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing the first PostgreSQL migration
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Main artifacts&lt;/th&gt;
&lt;th&gt;Setup encountered&lt;/th&gt;
&lt;th&gt;Managed history&lt;/th&gt;
&lt;th&gt;Rough time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flyway Desktop&lt;/td&gt;
&lt;td&gt;Desktop GUI&lt;/td&gt;
&lt;td&gt;Versioned SQL migration&lt;/td&gt;
&lt;td&gt;663 MB desktop installation, Redgate account, project, target connection&lt;/td&gt;
&lt;td&gt;Migration history&lt;/td&gt;
&lt;td&gt;~30 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Liquibase Community&lt;/td&gt;
&lt;td&gt;CLI&lt;/td&gt;
&lt;td&gt;Formatted SQL changelog and properties&lt;/td&gt;
&lt;td&gt;Java 17+, &lt;code&gt;JAVA_HOME&lt;/code&gt;, CLI, PostgreSQL driver, JDBC configuration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DATABASECHANGELOG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~1.5 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bytebase Cloud&lt;/td&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Plan containing SQL&lt;/td&gt;
&lt;td&gt;Account, workspace, instance connection, project, database transfer&lt;/td&gt;
&lt;td&gt;Changelog&lt;/td&gt;
&lt;td&gt;~30 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read these as onboarding observations, not speed rankings — a second attempt would be faster for every tool.&lt;/p&gt;

&lt;p&gt;Flyway Desktop and Bytebase Cloud landed in the same range by different routes: Flyway spent its time on installation and an account, Bytebase on workspace and project setup. Neither had a step where the documentation misled me. Liquibase's gap is a different kind — the &lt;code&gt;update&lt;/code&gt; command worked the first time I could actually run it, and the time went almost entirely into getting a runtime, a driver, and a project layout into the state its own guide assumed.&lt;/p&gt;

&lt;p&gt;The more useful distinction is what each tool teaches during the first migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flyway Desktop introduces versioned migration files through a visual project and target-database workflow.&lt;/li&gt;
&lt;li&gt;Liquibase Community introduces changesets and database tracking through an explicit local CLI toolchain.&lt;/li&gt;
&lt;li&gt;Bytebase Cloud introduces the change as a plan that moves through review, deployment, and centralized history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three apply and track the same change. What differs is where configuration lives, which dependencies the user has to manage, and how early review and collaboration appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this exercise does not compare
&lt;/h2&gt;

&lt;p&gt;This walkthrough does not evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;migration performance or throughput;&lt;/li&gt;
&lt;li&gt;rollback strategies;&lt;/li&gt;
&lt;li&gt;drift detection;&lt;/li&gt;
&lt;li&gt;complex or long-running schema changes;&lt;/li&gt;
&lt;li&gt;team collaboration and permissions;&lt;/li&gt;
&lt;li&gt;CI/CD integration;&lt;/li&gt;
&lt;li&gt;policy enforcement across environments;&lt;/li&gt;
&lt;li&gt;licensing or total cost of ownership.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those topics require a broader scenario than a single &lt;code&gt;CREATE TABLE&lt;/code&gt; statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next up: from test to production
&lt;/h2&gt;

&lt;p&gt;A first migration is a poor proxy for daily use. The interesting comparison is what happens when a change has to move from &lt;strong&gt;test to production&lt;/strong&gt;: pull requests or approvals, automated checks, and a deployment history someone else can audit. That is where migration tooling stops being a quickstart exercise and becomes a team practice, and it is what I plan to look at next.&lt;/p&gt;

&lt;p&gt;If you want a feature-by-feature look instead of a first-run diary, see &lt;a href="https://www.bytebase.com/blog/bytebase-vs-flyway" rel="noopener noreferrer"&gt;Bytebase vs. Flyway&lt;/a&gt;, &lt;a href="https://www.bytebase.com/blog/bytebase-vs-liquibase" rel="noopener noreferrer"&gt;Bytebase vs. Liquibase&lt;/a&gt;, and &lt;a href="https://www.bytebase.com/blog/flyway-vs-liquibase" rel="noopener noreferrer"&gt;Flyway vs. Liquibase&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.bytebase.com/blog/from-zero-to-first-migration/" rel="noopener noreferrer"&gt;Bytebase blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>DBHub vs Bytebase MCP: which one to point your agent at</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Thu, 13 Aug 2026 13:16:33 +0000</pubDate>
      <link>https://dev.to/bytebase/dbhub-vs-bytebase-mcp-which-one-to-point-your-agent-at-ana</link>
      <guid>https://dev.to/bytebase/dbhub-vs-bytebase-mcp-which-one-to-point-your-agent-at-ana</guid>
      <description>&lt;p&gt;We maintain two database MCP servers, &lt;a href="https://github.com/bytebase/dbhub" rel="noopener noreferrer"&gt;DBHub&lt;/a&gt; and the one built into &lt;a href="https://github.com/bytebase/bytebase" rel="noopener noreferrer"&gt;Bytebase&lt;/a&gt;. People keep asking which to use, so: &lt;strong&gt;DBHub when you're the only one affected, Bytebase MCP when other people are.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Note that's not the same as dev vs prod. A read-only replica only you touch is fine on DBHub; a staging database loaded with a copy of real customer data isn't. What matters is who pays if the agent does the worst thing it can do.&lt;/p&gt;

&lt;p&gt;The rest is one difference, and everything follows from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared account vs your account
&lt;/h2&gt;

&lt;p&gt;DBHub takes a connection string. In practice that's a shared service account, usually the one your app already uses, and every agent pointed at it connects as that same account.&lt;/p&gt;

&lt;p&gt;Bytebase MCP takes an OAuth login. The agent connects as you.&lt;/p&gt;

&lt;p&gt;So when your agent runs a &lt;code&gt;DELETE&lt;/code&gt; at 2am, DBHub's database logs &lt;code&gt;app_user&lt;/code&gt;, same as every other query from everyone else sharing it. You know it happened, not who it happened for, and you can't revoke it for one person without revoking it for everybody. Bytebase logs it under your account.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9hx7zntspvhe2d7rjd5c.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9hx7zntspvhe2d7rjd5c.png" alt="Two lanes with the same two people and the same Postgres database. Through DBHub both identities stop at the server and are replaced by one shared app_user, so the Postgres log shows the same name twice. Through Bytebase MCP both identities are carried through, so the Bytebase audit shows alice@corp and bob@corp separately along with what each one did." width="799" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DBHub
&lt;/h2&gt;

&lt;p&gt;One command, no server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @bytebase/dbhub@latest &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--dsn&lt;/span&gt; &lt;span class="s2"&gt;"postgres://user:pass@localhost:5432/mydb"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two tools by default, run SQL and search the schema, which keeps your context window free for the actual problem. Postgres, MySQL, MariaDB, SQL Server and SQLite, several connections in one process. You can start it read-only, cap the rows, set a query timeout.&lt;/p&gt;

&lt;p&gt;No identity, though. Read-only is a flag on the process, so it protects you from the agent, not from whoever started the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bytebase MCP
&lt;/h2&gt;

&lt;p&gt;Nothing to install. If you run Bytebase it's at &lt;code&gt;/mcp&lt;/code&gt;, and your agent inherits your account, which already has rules on it: masked columns come back &lt;code&gt;******&lt;/code&gt;, schema changes open a review instead of running, and every call is logged under your name.&lt;/p&gt;

&lt;p&gt;Worth saying plainly: it inherits &lt;em&gt;all&lt;/em&gt; your permissions. Connecting as yourself only helps if yourself isn't an admin.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;DBHub&lt;/th&gt;
&lt;th&gt;Bytebase MCP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connects as&lt;/td&gt;
&lt;td&gt;A shared account&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reads&lt;/td&gt;
&lt;td&gt;As stored&lt;/td&gt;
&lt;td&gt;Masked per policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writes&lt;/td&gt;
&lt;td&gt;Run immediately&lt;/td&gt;
&lt;td&gt;Open a review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit&lt;/td&gt;
&lt;td&gt;Database login records&lt;/td&gt;
&lt;td&gt;Every call, under your account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;A running Bytebase&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The catches
&lt;/h2&gt;

&lt;p&gt;DBHub has no per-person access to grant or revoke, no masking, and nothing between the agent and the table if writes are on.&lt;/p&gt;

&lt;p&gt;Bytebase MCP needs Bytebase running with an external URL, and it's HTTP only. You can't dry-run SQL, since the checks happen when the change is created. Results cap at 100 rows by default, 1,000 max, 30 second timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking
&lt;/h2&gt;

&lt;p&gt;Local Postgres, a scratch copy, a replica, read-only poking at prod: DBHub. Real customer data, masked columns, writes someone should look at first: Bytebase MCP.&lt;/p&gt;

&lt;p&gt;Both, honestly, is the normal answer. DBHub in your client config for local work, Bytebase MCP for anything shared. What to avoid is the middle, where a connection string that happens to reach production ends up in an MCP config because it was the fastest thing to paste.&lt;/p&gt;

</description>
      <category>database</category>
      <category>ai</category>
      <category>postgres</category>
      <category>devops</category>
    </item>
    <item>
      <title>🐘🤖 Top 3 Open Source Postgres MCP Servers in 2026</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:35:02 +0000</pubDate>
      <link>https://dev.to/bytebase/top-3-open-source-postgres-mcp-servers-in-2026-43g4</link>
      <guid>https://dev.to/bytebase/top-3-open-source-postgres-mcp-servers-in-2026-43g4</guid>
      <description>&lt;p&gt;At Bytebase we work on database access control all day, so pointing an AI agent at a real database is not a hypothetical for me. Picking a Postgres MCP server is easy right up until the database on the other end holds something you actually care about. What runs fine against a throwaway local database behaves very differently in production. That split, trusted local development vs. production, is the lens I use here. Most of these servers are great at the first and quietly dangerous at the second.&lt;/p&gt;

&lt;p&gt;Building one safely is harder than it looks. Anthropic's own reference Postgres server got archived after people noticed its "read-only" mode happily accepted &lt;code&gt;COMMIT; DROP SCHEMA public CASCADE;&lt;/code&gt;. Read-only, except when it wasn't. So I care less about the feature list and more about two questions: what can the agent actually do, and can you prove afterward what it did. The three servers below answer those very differently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://star-history.com/#crystaldba/postgres-mcp&amp;amp;supabase/mcp&amp;amp;pgplex/pgconsole&amp;amp;Date" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fstar-history.svg%26output%3Dpng%26w%3D1200" alt="Postgres MCP server GitHub star history" width="1200" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/pgplex/pgconsole" rel="noopener noreferrer"&gt;pgconsole&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;pgconsole is the newest of the three. It is a self-hosted Postgres console, a web SQL editor first, that also hands its connections to agents over a built-in MCP endpoint. The important part: the agent never sees a connection string. It gets a token, and every call runs through the same access rules and audit log as a human clicking around the console. The agent talks to pgconsole, and pgconsole talks to the database.&lt;/p&gt;

&lt;p&gt;Most database tools land at one of two extremes. Heavyweight platforms that need real infrastructure to stand up, or lightweight editors with no access controls at all. pgconsole sits in the awkward, useful middle: a Postgres editor with team controls built in, shipped as a single binary you run yourself.&lt;/p&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%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fpgconsole.svg%26output%3Dpng%26w%3D1200" 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%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fpgconsole.svg%26output%3Dpng%26w%3D1200" alt="pgconsole gives the agent its own identity, acting on behalf of a user with rights attenuated to least privilege" width="1200" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The access control is the heart of it, and it treats agents as first-class principals instead of bolting them on after the fact with built-in delegation and attenuation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One self-hosted binary. Connections, users, and access rules live in a versioned &lt;code&gt;pgconsole.toml&lt;/code&gt;, GitOps-style, and you run it with npx or Docker. No separate database to babysit.&lt;/li&gt;
&lt;li&gt;Fine-grained, default-deny access. Seven independent rights (read, write, DDL, admin, and more), scoped per connection, with nothing allowed unless a rule grants it. pgconsole parses every statement before it runs, so a destructive query cannot sneak through a read-only tool.&lt;/li&gt;
&lt;li&gt;Agents as first-class identities. An agent authenticates as its own principal, or acts on behalf of a user. In delegated mode it inherits that person's permissions, capped lower and never higher, and it loses access the moment the user does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;alice-claude&lt;/code&gt; agent acts for Alice, capped to read-only on a single connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[agents]]&lt;/span&gt;
&lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alice-claude"&lt;/span&gt;
&lt;span class="py"&gt;on_behalf_of&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"alice@example.com"&lt;/span&gt;
&lt;span class="py"&gt;permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c"&gt;# capped at or below Alice's own grants&lt;/span&gt;
&lt;span class="py"&gt;connections&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"prod"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c"&gt;# and scoped to a single connection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Best For:&lt;/strong&gt; teams that want a self-hosted Postgres editor and a genuinely governed agent path (distinct identities, delegation, least-privilege caps) without standing up a whole platform.&lt;/p&gt;

&lt;p&gt;One honest caveat: it is young. Around 120 stars, Apache-2.0, recent first releases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; the access model is the reason to run it. Distinct agent identity, on-behalf-of delegation, and attenuation that holds an agent at or below the human it acts for, all enforced by a SQL parser rather than by trust. If you remember one design from this post, make it this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/supabase/mcp" rel="noopener noreferrer"&gt;Supabase MCP&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Supabase MCP manages a whole Supabase project: auth, storage, edge functions, branches, not just the Postgres database underneath. That breadth is both the appeal and the cage. It only works on Supabase.&lt;/p&gt;

&lt;p&gt;It is a first-party server from Supabase, the open-source Firebase alternative, and it inherits the company's instinct to treat everything as one managed surface. That is why it reaches well past plain SQL into auth, storage, and functions.&lt;/p&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%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fsupabase.svg%26output%3Dpng%26w%3D1200" 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%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fsupabase.svg%26output%3Dpng%26w%3D1200" alt="Supabase MCP exposes the whole project surface, but only for Supabase-hosted databases" width="1200" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wide surface: 29 tools across eight feature groups (Account, Database, Debugging, Development, Edge Functions, Branching, Docs, Storage), covering migrations, TypeScript type generation, and serverless functions beyond plain SQL.&lt;/li&gt;
&lt;li&gt;Hosted, with OAuth 2.1 and automatic token refresh, so no local server, binaries, or hardcoded credentials.&lt;/li&gt;
&lt;li&gt;Stronger security defaults than most: project scoping, feature-group restriction, and a dedicated read-only role (&lt;code&gt;supabase_read_only_user&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best For:&lt;/strong&gt; teams already on Supabase who want their agent to manage the project, not just query it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; excellent if Supabase is your platform, irrelevant otherwise. Two things to know: the default tool set is heavy (~19.3k tokens, though selective loading cuts it to ~4.2k), and the Management API endpoint behind &lt;code&gt;execute_sql&lt;/code&gt; is still beta with no row limits. More guardrails than most. But like every server here, it mitigates prompt injection, it does not solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/crystaldba/postgres-mcp" rel="noopener noreferrer"&gt;Postgres MCP Pro&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Postgres MCP Pro, from Crystal DBA, is usually the first result when you search for a Postgres MCP server. It is host-neutral, point it at any Postgres, and it goes further than most. Where the others stop at "run this query," it brings a DBA's tuning toolkit.&lt;/p&gt;

&lt;p&gt;Crystal DBA open-sourced it as the on-ramp to its AI-driven Postgres tooling. Then Temporal &lt;a href="https://temporal.io/blog/temporal-and-the-next-frontier-scaling-ai-reliably" rel="noopener noreferrer"&gt;acquired the company&lt;/a&gt; in September 2025, and active development of the server has mostly stalled since. That matters, and I will come back to it.&lt;/p&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%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fpostgres-mcp-pro.svg%26output%3Dpng%26w%3D1200" 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%2Fwsrv.nl%2F%3Furl%3Dwww.bytebase.com%2Fcontent%2Fblog%2Ftop-open-source-postgres-mcp-servers%2Fpostgres-mcp-pro.svg%26output%3Dpng%26w%3D1200" alt="Postgres MCP Pro adds tuning analysis, but defaults to unrestricted read/write access" width="1200" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance analysis: index tuning, &lt;code&gt;EXPLAIN&lt;/code&gt; plan inspection, and database health checks. This is the real differentiator.&lt;/li&gt;
&lt;li&gt;Two access modes: unrestricted (full read/write, arbitrary SQL) and restricted (read-only, with query-time limits).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best For:&lt;/strong&gt; developers and DBAs who want the agent to diagnose a slow query or propose an index, not just read rows.&lt;/p&gt;

&lt;p&gt;Two things to weigh before reaching for it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It defaults to unrestricted, and every README example uses it. Out of the box the agent gets full write access and arbitrary SQL. Set &lt;code&gt;--access-mode=restricted&lt;/code&gt; for anything past a dev sandbox. Shipping with the safety off by default is a choice I disagree with.&lt;/li&gt;
&lt;li&gt;Development has stalled since the acquisition. The last tagged release (v0.3.0) was May 2025, and commits trailed off after the September 2025 Temporal deal (the last one landed in January 2026). Merged work like streamable-HTTP transport never made it into a release or the Docker image, &lt;a href="https://github.com/crystaldba/postgres-mcp/issues/162" rel="noopener noreferrer"&gt;people are asking for one&lt;/a&gt;, and 63 issues sit open, including a &lt;a href="https://github.com/crystaldba/postgres-mcp/issues/98" rel="noopener noreferrer"&gt;connection-pool leak under SSE&lt;/a&gt; that is the most-reacted of the bunch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; the tuning features are genuinely good, and still the reason to keep it around for hands-on work. But change the unrestricted default on day one, pin a known-good version, and factor in the stalled maintenance before you let it anywhere near production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Postgres MCP Server Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;Neutrality&lt;/th&gt;
&lt;th&gt;Governance&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pgconsole&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;Any Postgres&lt;/td&gt;
&lt;td&gt;Default-deny IAM, per-connection rights; agent identity, on-behalf-of, attenuation, audit&lt;/td&gt;
&lt;td&gt;Self-hosted teams wanting control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supabase MCP&lt;/td&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;2.7k&lt;/td&gt;
&lt;td&gt;Supabase-locked&lt;/td&gt;
&lt;td&gt;Read-only by default, scoped token; project scoping, read-only role&lt;/td&gt;
&lt;td&gt;Teams already on Supabase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres MCP Pro&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;2.9k&lt;/td&gt;
&lt;td&gt;Any Postgres&lt;/td&gt;
&lt;td&gt;Read/write by default, opt-in read-only; none (forwards the connection)&lt;/td&gt;
&lt;td&gt;DBA-style tuning + dev&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Star counts are from the time of writing and move fast. Treat them as a rough adoption signal, not a ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;None of these three is finished. Pick by where you sit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;pgconsole&lt;/strong&gt; has the strongest access model (separate agent identity, on-behalf-of, attenuation) but it is the youngest. Right for a self-hosted editor with a governed agent path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase MCP&lt;/strong&gt; is the most polished, as long as you live on Supabase. Right when you are already on Supabase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postgres MCP Pro&lt;/strong&gt; has the richest toolkit but lost its momentum to an acquisition. Right for diagnosing a slow query in dev, once you switch off the unrestricted default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are great on a laptop and get scarier in production, for the same structural reason. Simon Willison named it the &lt;a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/" rel="noopener noreferrer"&gt;lethal trifecta&lt;/a&gt;: an agent with access to private data, exposure to untrusted input, and a way to send data back out. A correctly scoped credential does not save you, because the attack arrives through the prompt, not the permissions. So the real test is not "can the agent query the database." It is "what happens when the prompt tells it to do something it shouldn't."&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
    <item>
      <title>🪄🐘 Postgres best practices I wish every app developer knew 🧑‍💻</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Sun, 14 Jun 2026 20:52:27 +0000</pubDate>
      <link>https://dev.to/bytebase/postgres-best-practices-i-wish-every-app-developer-knew-4f2d</link>
      <guid>https://dev.to/bytebase/postgres-best-practices-i-wish-every-app-developer-knew-4f2d</guid>
      <description>&lt;p&gt;At Bytebase we work with PostgreSQL every day, both inside our own product and across the databases our customers point us at. The same handful of mistakes shows up over and over, and almost none of them are exotic. They are small things an application developer controls and skips anyway.&lt;/p&gt;

&lt;p&gt;So this is not a database administration checklist. These are the five things I wish every app developer turned on by default. Each takes minutes, and each one buys you something concrete: a connection you can actually debug, a query that fails fast instead of hanging, a deploy that doesn't lock prod. Do them and your DBA stops paging you at 2am. There's a bonus index trick at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Set application_name for Better Debugging
&lt;/h2&gt;

&lt;p&gt;This is the cheapest win on the list, and the one people skip most. By default every connection from your fleet looks identical. When something melts down and you're staring at a wall of &lt;code&gt;pg_stat_activity&lt;/code&gt; rows, all the same user from the same pooler IP, you have no idea which service is the culprit. &lt;code&gt;application_name&lt;/code&gt; is a free label. Set it once per service and the noise turns into a name.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Implement
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL connection string:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://user:password@localhost:5432/mydb?application_name=user-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Node.js with pg:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mydb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myuser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mypassword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;application_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Python with psycopg2:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;

&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mydb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;myuser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mypassword&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;application_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-service&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Java with JDBC:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jdbc:postgresql://localhost:5432/mydb?ApplicationName=user-service"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Connection&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DriverManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getConnection&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"myuser"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mypassword"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Go with lib/pq:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"database/sql"&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="s"&gt;"github.com/lib/pq"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;connStr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"host=localhost dbname=mydb user=myuser password=mypassword application_name=user-service"&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"postgres"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;connStr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What You Get
&lt;/h3&gt;

&lt;p&gt;Now &lt;code&gt;pg_stat_activity&lt;/code&gt; actually tells you something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- See all active connections from your service&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;application_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;application_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'user-service'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Monitor long-running queries from specific services&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;application_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_activity&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'active'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;query_start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 seconds'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Configure PostgreSQL Statement and Lock Timeouts
&lt;/h2&gt;

&lt;p&gt;Postgres will wait forever if you let it. With no statement timeout, one bad query sits there holding a connection until a human notices. Do that across a pool and you don't get a slow app, you get an outage: every slot taken, nothing left to serve real traffic. Four timeouts decide how patient Postgres is willing to be. Set all four.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Four Critical Timeouts
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;statement_timeout&lt;/code&gt;. The ceiling on any single statement. This is the one to set first; it kills runaway queries before they take the pool down with them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lock_timeout&lt;/code&gt;. How long a statement waits for a lock before giving up, instead of blocking your whole request path behind someone else's transaction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;idle_in_transaction_timeout&lt;/code&gt;. Reaps a transaction that opened, grabbed locks, and then went quiet because the app forgot to commit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;transaction_timeout&lt;/code&gt;. The ceiling on an entire transaction, not just one statement (PostgreSQL 17+). Stops a long transaction from sitting on locks the whole time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to Configure
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;In connection string:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://user:pass@localhost/db?options=-c%20statement_timeout=30s%20-c%20lock_timeout=10s%20-c%20transaction_timeout=60s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;At transaction level:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;LOCAL&lt;/span&gt; &lt;span class="n"&gt;statement_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'30s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;LOCAL&lt;/span&gt; &lt;span class="n"&gt;lock_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'10s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;LOCAL&lt;/span&gt; &lt;span class="n"&gt;transaction_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'60s'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;-- PostgreSQL 17+&lt;/span&gt;
&lt;span class="c1"&gt;-- Your transaction operations here&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Recommended Values by Use Case
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;statement_timeout&lt;/th&gt;
&lt;th&gt;lock_timeout&lt;/th&gt;
&lt;th&gt;idle_in_transaction_timeout&lt;/th&gt;
&lt;th&gt;transaction_timeout&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Web Applications&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;td&gt;10s&lt;/td&gt;
&lt;td&gt;60s&lt;/td&gt;
&lt;td&gt;60s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Background Jobs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300s+&lt;/td&gt;
&lt;td&gt;30s&lt;/td&gt;
&lt;td&gt;300s&lt;/td&gt;
&lt;td&gt;600s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reporting/Analytics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0 (disabled)&lt;/td&gt;
&lt;td&gt;60s&lt;/td&gt;
&lt;td&gt;600s&lt;/td&gt;
&lt;td&gt;0 (disabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The logic is just patience matched to purpose. A user is staring at a spinner, so web requests should fail fast. Background jobs run unattended and can afford to wait a little longer for a lock. Analytics queries legitimately run for hours, so you disable the statement cap there and accept that those sessions are the exception, not the rule.&lt;/p&gt;



&lt;p&gt;To learn more, please refer &lt;a href="https://dev.to/blog/postgres-timeout"&gt;Postgres Timeout Explained&lt;/a&gt;.&lt;/p&gt;



&lt;h2&gt;
  
  
  3. Run Online Schema Migrations Without Downtime
&lt;/h2&gt;

&lt;p&gt;This is where a one-line migration becomes an incident. A statement that runs in 20ms on your laptop can lock a 50-million-row table for ten minutes in production, and for those ten minutes every read and write behind it queues up. The thing that actually takes prod down isn't the migration. It's the pile of normal traffic stuck waiting on its lock.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem with Standard Migrations
&lt;/h3&gt;

&lt;p&gt;Traditional migrations acquire exclusive locks that block all reads and writes to your tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- DON'T: This blocks ALL SELECT, INSERT, UPDATE, DELETE on users table&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;email_verified&lt;/span&gt; &lt;span class="nb"&gt;boolean&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- DON'T: This blocks ALL writes (INSERT, UPDATE, DELETE) on orders table for hours&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a small table you'll never notice. On a large one, the same statement blocks every query for minutes, the connection pool drains as traffic backs up behind it, and the stall cascades into every service that touches that table. One &lt;code&gt;ALTER&lt;/code&gt; takes down half the stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Online Migration Strategies
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="c1"&gt;-- Safe: Builds index without blocking reads/writes&lt;/span&gt;
   &lt;span class="c1"&gt;-- Note: Cannot be run inside a transaction&lt;/span&gt;
   &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;idx_orders_user_created&lt;/span&gt;
   &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;To learn more production tips, please refer &lt;a href="https://dev.to/blog/postgres-create-index-concurrently"&gt;Postgres CREATE INDEX CONCURRENTLY&lt;/a&gt;.&lt;/p&gt;



&lt;ol&gt;
&lt;li&gt;Add columns and constraints safely
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="c1"&gt;-- Step 1: Add constraint without validation (fast)&lt;/span&gt;
   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;email_verified_not_null&lt;/span&gt;
   &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email_verified&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;VALID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;-- Step 2: Validate constraint (can be done later, non-blocking)&lt;/span&gt;
   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;VALIDATE&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;email_verified_not_null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="c1"&gt;-- Step 3: Convert to NOT NULL constraint&lt;/span&gt;
   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;email_verified&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Split PostgreSQL Roles for Different Operations
&lt;/h2&gt;

&lt;p&gt;Most apps connect to Postgres as one user that can do everything, because that's what the quickstart told you to do. It works right up until a bug in your code runs a &lt;code&gt;DROP TABLE&lt;/code&gt; it should never have been allowed to run. Least privilege isn't bureaucracy here. It's the difference between a bug that returns a permission error and a bug that drops prod by accident.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem with Single "Super User" Approach
&lt;/h3&gt;

&lt;p&gt;Many applications use a single database user with broad permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- DON'T: Single user with excessive privileges&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You inherit four problems at once. The app carries DDL rights it never uses, so a single bug can reshape the schema. Every connection is the same identity, so in &lt;code&gt;pg_stat_activity&lt;/code&gt; you can't tell a runaway report apart from your write path. You can't tune connection settings per workload. And your audit trail says "the app did it", which tells you nothing. One identity, every risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three-Role Strategy
&lt;/h3&gt;

&lt;p&gt;Three roles cover almost every app. Think three keys, not one master key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read-Only Role - For analytics, reporting, read replicas&lt;/li&gt;
&lt;li&gt;Application Role - For normal CRUD operations&lt;/li&gt;
&lt;li&gt;Migration Role - For schema changes and DDL operations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Implementation Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Create the roles:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 1. Read-only role for analytics/reporting&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;app_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Automatically grant SELECT on future tables&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_reader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. Application role for normal operations&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Auto-grant on future objects&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 3. Migration role for schema changes&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;app_migrator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;myapp&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_migrator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_migrator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_migrator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;app_migrator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create actual users:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create users and assign roles&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;analytics_user&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'secure_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;app_reader&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;analytics_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;application_user&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'secure_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;app_writer&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;application_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;migration_user&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'secure_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="n"&gt;app_migrator&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;migration_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Use UPSERT for Idempotent Writes
&lt;/h2&gt;

&lt;p&gt;Networks drop, clients retry, two requests race. In production this isn't an edge case, it's Tuesday. UPSERT (&lt;code&gt;INSERT ... ON CONFLICT&lt;/code&gt;) lets you write code that doesn't care whether the row already exists. A retry can't create a duplicate, a race resolves itself, and the "create or update" branching you'd otherwise write in the app disappears. The database settles the conflict atomically, which is exactly where you want that decision made.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ignore Duplicates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pattern 1: Ignore duplicates&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'John Doe'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update on Conflict
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pattern 2: Update on conflict&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'john@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'John Smith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conditional Update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Pattern 3: Conditional update&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;user_stats&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;login_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
  &lt;span class="n"&gt;login_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;last_login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_stats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: Implement "Invisible" Indexes
&lt;/h2&gt;

&lt;p&gt;PostgreSQL doesn't ship invisible indexes the way MySQL 8 does. But you can fake it by flipping the &lt;code&gt;indisvalid&lt;/code&gt; flag in the &lt;code&gt;pg_index&lt;/code&gt; catalog, which takes an index out of the planner's consideration without dropping it. Handy when you suspect an index isn't earning its keep and want to confirm that before you actually remove it. Just know you're poking a system catalog directly, so keep it to staging or a maintenance window.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making an Index "Invisible"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Disable an existing index (make it invisible to the query planner)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;indisvalid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;indexrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'idx_orders_status'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Making the Index "Visible" Again
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Re-enable the index (make it visible to the query planner)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;indisvalid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;indexrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'idx_orders_status'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking Index Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- See which indexes are currently disabled&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;indexname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;indisvalid&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;is_valid&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_indexes&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pg_indexes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;pg_index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;indisvalid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: PostgreSQL itself uses the &lt;code&gt;indisvalid&lt;/code&gt; flag internally. When you run &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt;, PostgreSQL automatically sets &lt;code&gt;indisvalid = false&lt;/code&gt; until the index is fully built.&lt;/p&gt;

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

&lt;p&gt;None of this is database administration. It's five things an application developer controls and usually leaves on the floor: name your connections, set your timeouts, migrate online, split your roles, write idempotent inserts. Start with &lt;code&gt;application_name&lt;/code&gt; and timeouts. They take ten minutes and pay you back the first time something breaks at 2am.&lt;/p&gt;

&lt;p&gt;None of these will win you praise in a design review. They just quietly keep you out of the incident channel, which is the whole point.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
    </item>
    <item>
      <title>⚙️🔥 Automating Database Schema Change workflow Using GitHub Actions 🐙🚀</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Wed, 20 Mar 2024 10:20:04 +0000</pubDate>
      <link>https://dev.to/bytebase/automating-database-schema-change-workflow-using-github-actions-4838</link>
      <guid>https://dev.to/bytebase/automating-database-schema-change-workflow-using-github-actions-4838</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Tutorial repository &lt;a href="https://github.com/bytebase/ci-example" rel="noopener noreferrer"&gt;https://github.com/bytebase/ci-example&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Developers like to keep their schema migration scripts in Git along with the application code. Thus the migration scripts will be reviewed and version-controlled in the same way as the application code.&lt;br&gt;
However, developers still have to manually paste the migration script into their SQL client or ask the DBAs to run it against the target database. This is inefficient and error-prone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if you paste the wrong script / miss the script?&lt;/li&gt;
&lt;li&gt;What if you run the script against the wrong database?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial teaches how to get rid of this manual process and fully automate database schema change&lt;br&gt;
using GitHub Actions and Bytebase API.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fworkflow.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fworkflow.webp" alt="workflow" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Above shows a typical workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Developer creates a PR containing the migration script. After PR is created, it triggers a GitHub&lt;br&gt;
Action to lint the SQL by calling Bytebase &lt;a href="https://www.bytebase.com/docs/api/sql-review/" rel="noopener noreferrer"&gt;SQL Review API&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TL approves the PR.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Upon PR approval, it triggers a GitHub Action to create a Bytebase rollout issue.&lt;br&gt;
The issue contains the migration script changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Depending on the configured &lt;a href="https://www.bytebase.com/docs/administration/custom-approval/" rel="noopener noreferrer"&gt;approval&lt;/a&gt; and &lt;a href="https://www.bytebase.com/docs/administration/environment-policy/rollout-policy/" rel="noopener noreferrer"&gt;rollout policy&lt;/a&gt;, it may require manual approval and rollout from DBA. Another GitHub Action is configured to block the PR merge until Bytebase rolls out the schema migration. Sometimes, a PR contains both code and schema changes. This setup guarantees &lt;strong&gt;the schema migration is applied before the code deployment&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bytebase deploys the schema change and marks the issue as Done.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PR re-runs the migration status check and now it turns green.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Now PR can be merged.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Prepare Bytebase
&lt;/h2&gt;

&lt;p&gt;Suppose Bytebase is running at &lt;code&gt;https://bytebase.example.com/&lt;/code&gt;. To begin, we'll first set up the necessary data to support our API interactions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Service account&lt;/strong&gt;: As an admin, we add a &lt;a href="https://www.bytebase.com/docs/api/authentication/#service-account" rel="noopener noreferrer"&gt;service account&lt;/a&gt; &lt;code&gt;ci@service.bytebase.com&lt;/code&gt; with the &lt;code&gt;Workspace DBA&lt;/code&gt; role, which will be used to authenticate the API calls.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-workspace-members-ci.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-workspace-members-ci.webp" alt="bb-workspace-members-ci" width="800" height="100"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;To limit the service account permissions, you can choose to grant &lt;code&gt;Workspace Member&lt;/code&gt; instead of &lt;code&gt;Workspace DBA&lt;/code&gt;.&lt;br&gt;
   And then in the particular project, grant the account the permission to create an issue.&lt;/p&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A database in a project&lt;/strong&gt;: We have a project called &lt;code&gt;Example&lt;/code&gt;, and a database: &lt;code&gt;example&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-project-database.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-project-database.webp" alt="bb-project-database" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Prepare GitHubs Actions
&lt;/h2&gt;

&lt;p&gt;Go and check the example on GitHub &lt;a href="https://github.com/bytebase/ci-example" rel="noopener noreferrer"&gt;https://github.com/bytebase/ci-example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The repository contains several GitHub Action workflows, you may go to &lt;code&gt;.github/workflows&lt;/code&gt; to view.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-workflows.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-workflows.webp" alt="gh-workflows" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will use the following workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bytebase-sql-review.yml&lt;/code&gt;: Triggered on PR change. Thus any SQL review violation will block the PR.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bytebase-upsert-migration.yml&lt;/code&gt; Triggered on PR approval. Creates the Bytebase migration issue after approval.
And whenever the migration scripts change afterwards, the migration issue will also be updated accordingly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bytebase-check-migration-status.yml&lt;/code&gt;: Triggered on PR change. Thus PR will be blocked until migration completes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Sample Workflow, Four Phases
&lt;/h2&gt;

&lt;p&gt;To illustrate the workflow, we have divided it into four phases to showcase the database schema change process.&lt;/p&gt;
&lt;h3&gt;
  
  
  Phase 1: Not passing SQL review on GitHub
&lt;/h3&gt;

&lt;p&gt;Before we delve into the workflow, let's set up the SQL Review policy in Bytebase. The example database is on the &lt;code&gt;Prod&lt;/code&gt; environment, where we will configure SQL review policy. Here we have a policy that checks for &lt;code&gt;NOT NULL&lt;/code&gt; constraints, which we will violate in the PR.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-environment-sql-review.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-environment-sql-review.webp" alt="bb-environment-sql-review" width="800" height="287"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-sql-review-policy-not-null.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-sql-review-policy-not-null.webp" alt="bb-sql-review-policy-not-null" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Returning to GitHub Actions and digging into the code, the &lt;code&gt;bytebase-sql-review.yml&lt;/code&gt; workflow is triggered on PR change. It scans the SQL files named following the pattern &lt;code&gt;**.up.sql&lt;/code&gt; in the PR and reports any SQL review policy violations.&lt;/p&gt;

&lt;p&gt;Configure the environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;bytebase-sql-review&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;BYTEBASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://bytebase-ci.zeabur.app"&lt;/span&gt;
         &lt;span class="na"&gt;BYTEBASE_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ci@service.bytebase.com"&lt;/span&gt;
         &lt;span class="na"&gt;DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instances/prod-instance/databases/example"&lt;/span&gt;
   &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After authentication, we call the Bytebase API &lt;code&gt;/sql/check&lt;/code&gt; to lint the migration files. We parse the response&lt;br&gt;
and emit GitHub inline annotations for each advice and mark the check as failed if any &lt;code&gt;ERROR&lt;/code&gt; or &lt;code&gt;WARNING&lt;/code&gt; is found.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SQL Review&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Bytebase&lt;/span&gt;
        &lt;span class="s"&gt;...&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Review&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;review&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/sql-review&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/*.up.sql"&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.BYTEBASE_URL }}&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.login.outputs.token }}&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"Accept-Encoding":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"deflate,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gzip"}'&lt;/span&gt;
          &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.DATABASE }}&lt;/span&gt;
      &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create a &lt;a href="https://github.com/bytebase/github-action-example/pull/15" rel="noopener noreferrer"&gt;PR&lt;/a&gt; with several SQL files, and it triggers both &lt;code&gt;bytebase-sql-review.yml&lt;/code&gt; and &lt;code&gt;bytebase-check-migration-status.yml&lt;/code&gt;. After these checks are completed, the PR is blocked due to failures.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-pr1-blocked.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-pr1-blocked.webp" alt="gh-pr1-blocked" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;Details&lt;/strong&gt; for SQL Review.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-sql-review-warning.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-sql-review-warning.webp" alt="gh-sql-review-warning" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may also go to &lt;strong&gt;Files changed&lt;/strong&gt; to view the annotations.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-sql-review-warning-file-annotation.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-sql-review-warning-file-annotation.webp" alt="gh-sql-review-warning-file-annotation" width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Passing SQL review and waiting for TL's approval on GitHub
&lt;/h3&gt;

&lt;p&gt;We then fix the SQL files and pushes. After completing these checks, the PR is still blocked due to failures, but this time SQL review has passed.&lt;/p&gt;

&lt;p&gt;In real-life scenarios, the PR also encompasses application code. Because the SQL migration has passed the basic SQL review checks, it is now time for a tech leader to &lt;strong&gt;approve&lt;/strong&gt; this PR.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-waiting-for-approval.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-waiting-for-approval.webp" alt="gh-waiting-for-approval" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The developer who creates the PR assigns the tech leader to review on GitHub.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-add-reviewers.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-add-reviewers.webp" alt="gh-add-reviewers" width="712" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: TL approves on GitHub and migration issue is created in Bytebase
&lt;/h3&gt;

&lt;p&gt;The assigned tech leader approves the PR, and another workflow &lt;code&gt;bytebase-upsert-migration.yml&lt;/code&gt; is triggered.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-waiting-for-check.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-waiting-for-check.webp" alt="gh-waiting-for-check" width="800" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It checks the SQL files named like &lt;code&gt;**.up.sql&lt;/code&gt; within the pull request and creates a rollout issue in Bytebase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;bytebase-upsert-migration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="c1"&gt;# Runs only if PR is approved and target branch is main&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event.review.state == 'approved' &amp;amp;&amp;amp; github.event.pull_request.base.ref == 'main'&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;BYTEBASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://bytebase-ci.zeabur.app"&lt;/span&gt;
      &lt;span class="na"&gt;BYTEBASE_SERVICE_ACCOUNT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ci@service.bytebase.com"&lt;/span&gt;
      &lt;span class="na"&gt;PROJECT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example"&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;instances/prod-instance/databases/example"&lt;/span&gt;
      &lt;span class="na"&gt;ISSUE_TITLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.repository&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}#${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.pull_request.number&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.pull_request.title&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="na"&gt;DESCRIPTION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Triggered&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.repository.html_url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/pull/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.pull_request.number&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github.event.pull_request.title&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upsert Migration&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to Bytebase and view the created issue, which consists of two tasks corresponding to the presence of two &lt;code&gt;**.up.sql&lt;/code&gt; files in the PR.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-issue-user-post.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-issue-user-post.webp" alt="bb-issue-user-post" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may notice there is an approval flow attached to the created issue, that's because we set up a default &lt;a href="https://www.bytebase.com/docs/administration/custom-approval/" rel="noopener noreferrer"&gt;custom approval flow for DDL&lt;/a&gt;.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-custom-approval.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-custom-approval.webp" alt="bb-custom-approval" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 4: Migration completed and PR is mergable on GitHub
&lt;/h3&gt;

&lt;p&gt;After the DBA approves and rolls out the migrations, the issue status will become &lt;code&gt;Done&lt;/code&gt;.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-issue-done.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fbb-issue-done.webp" alt="bb-issue-done" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go back to GitHub, click &lt;strong&gt;Details&lt;/strong&gt; for the failed &lt;code&gt;bytebase-check-migration-status.yml&lt;/code&gt; workflow, and then click &lt;strong&gt;Re-run all jobs&lt;/strong&gt;.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-pr2-blocked.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-pr2-blocked.webp" alt="gh-pr2-blocked" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-re-run.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-re-run.webp" alt="gh-re-run" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It checks the migration status in Bytebase and return &lt;code&gt;pass&lt;/code&gt; if it's &lt;code&gt;Done&lt;/code&gt;, indicating the database migration has been completed. The PR is now ready to be merged, which means the application code is ready to be deployed.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-re-run-pass.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-re-run-pass.webp" alt="gh-re-run-pass" width="800" height="255"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-all-pass-ready-merge.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fgithub-ci%2Fgh-all-pass-ready-merge.webp" alt="gh-all-pass-ready-merge" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Keep in mind that workflows can be tuned according to your organization's needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can attach the workflow to different branches depending on your branching strategy (e.g. trunk-based or not).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can use different migration file formats and different migration file structures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can determine when to create the migration issue, upon PR approval or creation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whatever workflow you choose, with the help of GitHub Actions and Bytebase API, you can now keep your migration scripts in the repository, let the migration scripts go through the same code review process and automate the schema migration deployment.&lt;/p&gt;

</description>
      <category>gitops</category>
      <category>devops</category>
      <category>database</category>
      <category>github</category>
    </item>
    <item>
      <title>🔄 How to Synchronize Database Schemas🔥🚀</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Fri, 01 Mar 2024 10:21:45 +0000</pubDate>
      <link>https://dev.to/bytebase/how-to-synchronize-database-schemas-j9l</link>
      <guid>https://dev.to/bytebase/how-to-synchronize-database-schemas-j9l</guid>
      <description>&lt;p&gt;This tutorial will walk you through the typical use cases for &lt;strong&gt;database schema synchronization&lt;/strong&gt; in Bytebase with &lt;strong&gt;pure UI operations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Sync Schema&lt;/strong&gt; feature in Bytebase supports copying a specific schema version from one database to multiple others. Without it, developers have to write SQL statements cautiously and apply them manually. Additionally, this feature may also be used for rollback purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;Make sure you installed &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy and paste the commands to start one Bytebase via Docker.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   docker run --init \
   --name bytebase \
   --publish 8080:8080 \
   --volume ~/.bytebase/data:/var/opt/bytebase \
   bytebase/bytebase:%%bb_version%%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Register and sign in &lt;strong&gt;Bytebase Console&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Case 1 - Sync Schema to a New Database
&lt;/h2&gt;

&lt;p&gt;When you set up a new environment or find the current database is nearing capacity, it becomes necessary to establish a new database and synchronize the schema from the original database.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go into &lt;code&gt;Sample Project&lt;/code&gt;, there are two databases &lt;code&gt;hr_test&lt;/code&gt; on &lt;code&gt;Test&lt;/code&gt; environment and &lt;code&gt;hr_prod&lt;/code&gt; on &lt;code&gt;Prod&lt;/code&gt; environment. Let's create another one on &lt;code&gt;Prod&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &lt;strong&gt;New DB&lt;/strong&gt;, fill in the form as follows and click &lt;strong&gt;Create&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;code&gt;hr_prod_2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment&lt;/strong&gt;: &lt;code&gt;Prod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance&lt;/strong&gt;: &lt;code&gt;Prod Sample Instance&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database owner name&lt;/strong&gt;: &lt;code&gt;bbsample&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-new-db-prod-2.webp" alt="bb-new-db-prod-2" width="800" height="375"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An issue will be created and executed automatically, and the database &lt;code&gt;hr_prod_2&lt;/code&gt; will be ready when the issue is &lt;code&gt;Done&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-new-db-prod-2-issue.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-new-db-prod-2-issue.webp" alt="bb-new-db-prod-2-issue" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Within the project, click &lt;strong&gt;Sync Schema&lt;/strong&gt;, choose &lt;code&gt;hr_prod&lt;/code&gt; as the source schema, and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-source.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-source.webp" alt="bb-sync-schema-source" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You may select as many target databases as you like to sync to. Here we select all (including the source) and click &lt;strong&gt;Select&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-all-selected.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-all-selected.webp" alt="bb-sync-schema-all-selected" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bytebase will calculate the schema differences between the source and target databases, and generate the suggested DDL statements. Click &lt;strong&gt;Preview Issue&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-before-preview.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-before-preview.webp" alt="bb-sync-schema-before-preview" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An issue with the generated DDL is created automatically. Click &lt;strong&gt;Rollout&lt;/strong&gt; ignoring the SQL review warning. After the issue execution is &lt;code&gt;Done&lt;/code&gt;, the schema synchronization is completed.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-issue-rollout-anyway.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-issue-rollout-anyway.webp" alt="bb-issue-rollout-anyway" width="800" height="385"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Case 2 - Sync Schema from One to Many
&lt;/h2&gt;

&lt;p&gt;During the product release process, for different environments, such as dev, staging and prod, you'll need to do schema synchronization. Furthermore, for the prod environment, databases with identical schemas, such as SaaS, and multi-region game deployment, you may also need to sync the schema to multiple databases at the same time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Within the project, select &lt;code&gt;hr_test&lt;/code&gt; and click &lt;strong&gt;Edit Schema&lt;/strong&gt;. With the help of Schema Editor, add a new column &lt;code&gt;email&lt;/code&gt; as follows and click &lt;strong&gt;Preview issue&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;code&gt;email&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type&lt;/strong&gt;: &lt;code&gt;text&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default&lt;/strong&gt;: &lt;code&gt;Empty string&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not Null&lt;/strong&gt;: &lt;code&gt;checked&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-schema-editor-email.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-schema-editor-email.webp" alt="bb-schema-editor-email" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;An issue is created and executed automatically. After it's &lt;code&gt;Done&lt;/code&gt;, the new column &lt;code&gt;email&lt;/code&gt; is added.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-issue-add-email-done.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-issue-add-email-done.webp" alt="bb-issue-add-email-done" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Within the project, click &lt;strong&gt;Sync Schema&lt;/strong&gt;, choose &lt;code&gt;hr_test&lt;/code&gt; as the source schema and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-email-source.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-email-source.webp" alt="bb-sync-schema-email-source" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;hr_prod&lt;/code&gt; and &lt;code&gt;hr_prod_2&lt;/code&gt; as the target databases. Schema differences are calculated and the suggested DDL statements are generated. Click &lt;strong&gt;Preview issue&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-add-email.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-add-email.webp" alt="bb-sync-schema-add-email" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An issue with the generated DDL is created automatically. Because two databases need to be synced, there're two task blocks. After they execute one by one, the issue is &lt;code&gt;Done&lt;/code&gt;, and &lt;code&gt;email&lt;/code&gt; is added.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-issue-add-email-prod.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-issue-add-email-prod.webp" alt="bb-issue-add-email-prod" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Case 3 - Rollback to Previous Schema Versions
&lt;/h2&gt;

&lt;p&gt;In Bytebase &lt;strong&gt;Community Plan&lt;/strong&gt;, you can choose the latest history version; In &lt;strong&gt;Pro Plan&lt;/strong&gt; or &lt;strong&gt;Enterprise Plan&lt;/strong&gt;, you can choose an arbitrary schema version from the full migration history. Which means you can roll back to any previous schema version.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;After upgrading to &lt;strong&gt;Enterprise Plan&lt;/strong&gt;, go into &lt;code&gt;Sample Project&lt;/code&gt;, click &lt;strong&gt;Sync Schema&lt;/strong&gt; and select &lt;code&gt;hr_test&lt;/code&gt; as the source schema. When you choose the schema version, you will see the following prompt. That's because we haven't assigned the enterprise license to database instances.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-test-lock.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-test-lock.webp" alt="bb-sync-test-lock" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click on the lock sign, select both two instances, and click &lt;strong&gt;Confirm&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-manage-license.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-manage-license.webp" alt="bb-manage-license" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the license is assigned, you can choose the schema version freely, we'll choose the previous version and click &lt;strong&gt;Next&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-prev.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-schema-prev.webp" alt="bb-sync-schema-prev" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose the same database &lt;code&gt;hr_test&lt;/code&gt; as the target, and click &lt;strong&gt;Select&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-rollback-target.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-rollback-target.webp" alt="bb-sync-rollback-target" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bytebase will calculate the schema differences between the source and target databases, and generate the suggested DDL statements. Here means if you want to roll back to the previous version, you'll need to delete this line. Click &lt;strong&gt;Preview Issue&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-rollback-preview.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-rollback-preview.webp" alt="bb-sync-rollback-preview" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;, an issue with the generated DDL is created and rollout automatically. After the issue execution is &lt;code&gt;Done&lt;/code&gt;, the schema rolls back to the previous version.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-rollback-done.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-synchronize-database-schemas%2Fbb-sync-rollback-done.webp" alt="bb-sync-rollback-done" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Is the experience smooth? Or do you encounter any problems? Feel free to join our &lt;a href="https://discord.gg/huyw7gRsyA" rel="noopener noreferrer"&gt;Discord Group&lt;/a&gt; to talk about it!&lt;/p&gt;

</description>
      <category>database</category>
      <category>mysql</category>
      <category>postgres</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>🛡️4 Top Database Security Tools in 2024 🏆🔥</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Thu, 01 Feb 2024 10:04:03 +0000</pubDate>
      <link>https://dev.to/bytebase/4-top-database-security-tools-22jj</link>
      <guid>https://dev.to/bytebase/4-top-database-security-tools-22jj</guid>
      <description>&lt;p&gt;In today's digital age, where data is as valuable as oil, protecting this crucial asset is essential for every organization. Threats arise from external sources like hackers and malware, as well as internal ones, including human errors and malicious insiders. This article will explore leading database security tools designed to shield your data from these varied risks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Networking - Tailscale
&lt;/li&gt;
&lt;li&gt;Database credential management - Infisical
&lt;/li&gt;
&lt;li&gt;On-demand database access - Indent
&lt;/li&gt;
&lt;li&gt;All-in-one human-to-db operation - Bytebase
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tailscale
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; is a VPN service that makes the devices and applications you own accessible anywhere in the world, securely and effortlessly. It enables encrypted point-to-point connections using the open source WireGuard® protocol, which means only devices on your private network can communicate with each other.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Ftailscale-home-img.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Ftailscale-home-img.webp" alt="tailscale-home-img" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When it comes to databases, Tailscale can be used to connect to your database from any location globally, without having to expose your database to the public internet or setting up extra SSH tunnels.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Ftailscale-home-slogan.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Ftailscale-home-slogan.webp" alt="tailscale-home-slogan" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its free tier accommodates up to 3 users and 100 devices, which is for individuals or small businesses. For additional capabilities like Access control and Audit logs, paid plans start at $6 per user per month.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Ftailscale-pricing.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Ftailscale-pricing.webp" alt="tailscale-pricing" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Infisical
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://infisical.com/" rel="noopener noreferrer"&gt;Infisical&lt;/a&gt; is an open-source, end-to-end encrypted secret management platform for storing, managing, and syncing application configuration and secrets like API keys, database credentials, and environment variables across applications and infrastructure. It is an alternative to HashiCorp Vault and AWS Secrets Manager.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Finfisical-one-dashboard.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Finfisical-one-dashboard.webp" alt="infisical-one-dashboard" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You have the option to host Infisical on your premises or utilize their cloud offering.&lt;/p&gt;

&lt;p&gt;With its help, your database credentials can be stored in a secure vault and be accessed by your application only when needed. In this way, you can avoid storing your database credentials in your application code or configuration files. Access is facilitated through CLI, SDK, Docker, Kubernetes, or REST API.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Finfisical-dashboard-secrets.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Finfisical-dashboard-secrets.webp" alt="infisical-dashboard-secrets" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The free tier supports up to 5 developers and 3 environments, catering to hobbyists. For features such as Access Controls and Audit Logs, paid plans start at $6 per developer per month.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Finfisical-pricing.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Finfisical-pricing.webp" alt="infisical-pricing" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Indent
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://indent.com/" rel="noopener noreferrer"&gt;Indent&lt;/a&gt; provides team members with on-demand access to cloud apps and infrastructure in seconds. They aptly describe this innovation as the &lt;strong&gt;request access button&lt;/strong&gt; for any software or service utilized by the company.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Findent-button.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Findent-button.webp" alt="indent-button" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the context of database access management, Indent provides you the capability to grant time-bound, granular access to systems that handle customer data. It offers three methods of integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero Trust Network (ZTN) - e.g. Tailscale&lt;/li&gt;
&lt;li&gt;Identity Group - e.g. Okta&lt;/li&gt;
&lt;li&gt;Custom Integration&lt;/li&gt;
&lt;/ul&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Findent-graph.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Findent-graph.webp" alt="indent-graph" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Indent provides a secure-by-default free tier suitable for small teams on a trial basis. For those requiring more, the paid plans begin at $8 per user per month.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Findent-pricing.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Findent-pricing.webp" alt="indent-pricing" width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bytebase
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://bytebase.com" rel="noopener noreferrer"&gt;Bytebase&lt;/a&gt; is an open-source database DevOps tool, it's the GitLab/GitHub for managing databases throughout the application development lifecycle. It offers a web-based collaboration workspace for DBAs, Developers and platform engineers. It consolidates disparate DB tools&lt;br&gt;
such as DBeaver, Liquibase, Flyway into a single place.&lt;/p&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%2Fbytebase.com%2Fimages%2Freplaced-tools.webp" 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%2Fbytebase.com%2Fimages%2Freplaced-tools.webp" alt="replaced-tools" width="771" height="718"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seeks to encompass all interactions between humans and databases across every database, including managing changes, querying data, handling access, and beyond. Thanks to its features such as SQL Lint, Database CI/CD, Data Access Control, Data Masking, and more, you can be confident that all your database activities are executed via standardized procedures and completely auditable.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Fbb-3-op.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Fbb-3-op.webp" alt="bb-3-op" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Community Plan, you can have up to 10 users and up to 5 databases for free. For medium to large organizations, the Enterprise plan offers advanced options like Data Access Control, Audit Log, and additional features.&lt;/p&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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Fbb-pricing-995.webp" 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%2Fbytebase.com%2Fcontent%2Fblog%2Ftop-database-security-tools%2Fbb-pricing-995.webp" alt="bb-pricing-995" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we have explored four database security tools that can help you protect your database from external and internal threats. Each tool offers its unique strengths; by understanding your specific needs, you can choose the one that most aligns with your requirements.&lt;/p&gt;

</description>
      <category>database</category>
      <category>security</category>
      <category>data</category>
      <category>secops</category>
    </item>
    <item>
      <title>🐣Your First Database Schema Change in 5 Minutes with Bytebase</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Thu, 18 Jan 2024 06:42:04 +0000</pubDate>
      <link>https://dev.to/bytebase/your-first-database-schema-change-in-5-minutes-with-bytebase-44a0</link>
      <guid>https://dev.to/bytebase/your-first-database-schema-change-in-5-minutes-with-bytebase-44a0</guid>
      <description>&lt;p&gt;In this tutorial, you'll use the sample databases Bytebase provides by default to get familiar with the product in the quickest way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Run via Docker
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install and start &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Open Terminal to run the command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  docker run --init \
    --name bytebase \
    --publish 8080:8080 \
    --volume ~/.bytebase/data:/var/opt/bytebase \
    bytebase/bytebase:2.13.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the Terminal shows the following message, the execution is successful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;██████╗ ██╗   ██╗████████╗███████╗██████╗  █████╗ ███████╗███████╗
██╔══██╗╚██╗ ██╔╝╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝
██████╔╝ ╚████╔╝    ██║   █████╗  ██████╔╝███████║███████╗█████╗
██╔══██╗  ╚██╔╝     ██║   ██╔══╝  ██╔══██╗██╔══██║╚════██║██╔══╝
██████╔╝   ██║      ██║   ███████╗██████╔╝██║  ██║███████║███████╗
╚═════╝    ╚═╝      ╚═╝   ╚══════╝╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝

  Version 2.13.1 has started on port 8080 🚀

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have Bytebase running in Docker.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fdk-bb-running.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fdk-bb-running.webp" alt="dk-bb-running" width="800" height="62"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Bytebase in &lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;localhost:8080&lt;/a&gt;, fill in the fields and click &lt;strong&gt;Create admin account&lt;/strong&gt;. You'll be redirected to the workspace.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-register.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-register.webp" alt="bb-register" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Follow the &lt;strong&gt;Quikstart&lt;/strong&gt; guide on the bottom to click around or dismiss it by now. You can click your avatar on top right and click &lt;strong&gt;Quickstart&lt;/strong&gt; on the dropdown menu to reopen it later.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-first-workspace.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-first-workspace.webp" alt="bb-first-workspace" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - One Issue with Two Stages
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;My Issues&lt;/strong&gt; on the left sidebar, and click the issue &lt;code&gt;SAM-101&lt;/code&gt; which is created by default.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-my-issues.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-my-issues.webp" alt="bb-my-issues" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The issue is &lt;code&gt;waiting to rollout&lt;/code&gt;. There's a pipeline consisting of two stages:

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Test Stage&lt;/strong&gt;: apply to database &lt;code&gt;hr_test&lt;/code&gt; on &lt;code&gt;Test Sample instance&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prod Stage&lt;/strong&gt;: apply to database &lt;code&gt;hr_prod&lt;/code&gt; on &lt;code&gt;Prod Sample instance&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;Test&lt;/code&gt; stage is &lt;code&gt;active&lt;/code&gt; by default.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-test.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-test.webp" alt="bb-issue-test" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Prod Stage&lt;/strong&gt; to switch to it, and you will see the two stages share the same SQL but to different databases. You may also notice there's a warning sign for SQL review on the &lt;strong&gt;Prod&lt;/strong&gt; stage. That's because when the issue is created, Bytebase will run task checks automatically. SQL review is one of them.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-prod.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-prod.webp" alt="bb-issue-prod" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the warning sign to see the details. If you wonder why only &lt;strong&gt;Prod Stage&lt;/strong&gt; has the warning sign, it's because by default SQL Review is only configured for &lt;code&gt;Prod&lt;/code&gt; environment. You can click the &lt;strong&gt;Settings&lt;/strong&gt; (gear) on the top right, and click &lt;strong&gt;Security &amp;amp; Policy&lt;/strong&gt; &amp;gt; &lt;strong&gt;SQL Review&lt;/strong&gt; to have a look.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-sql-review-not-null.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-sql-review-not-null.webp" alt="bb-sql-review-not-null" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Roll out on Test Stage
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Switch back to &lt;strong&gt;Test Stage&lt;/strong&gt; and click &lt;strong&gt;Rollout&lt;/strong&gt;. Click &lt;strong&gt;Rollout&lt;/strong&gt; on the confirmation dialog.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-test-rollout.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-test-rollout.webp" alt="bb-test-rollout" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When the SQL is applied, there will be a checkmark on the &lt;strong&gt;Test Stage&lt;/strong&gt;. Click &lt;strong&gt;View change&lt;/strong&gt; and you'll see the diff.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-test-done.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-test-done.webp" alt="bb-issue-test-done" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fissue-snapshot-diff.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fissue-snapshot-diff.webp" alt="issue-snapshot-diff" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - Roll out on Prod Stage
&lt;/h2&gt;

&lt;p&gt;There are two ways to roll out on &lt;strong&gt;Prod Stage&lt;/strong&gt; regarding the SQL review result.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you are confident with the SQL, you can click &lt;strong&gt;Rollout&lt;/strong&gt; directly. Check the &lt;strong&gt;Rollout anyway&lt;/strong&gt;, and click &lt;strong&gt;Rollout&lt;/strong&gt; on the confirmation dialog.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-prod-anyway.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-prod-anyway.webp" alt="bb-issue-prod-anyway" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Another way is to edit the SQL. Click &lt;strong&gt;Edit&lt;/strong&gt; on top of the SQL, and add the &lt;code&gt;NOT NULL&lt;/code&gt;. It will look like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;   &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;employee&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt;, the checks will be run again. This time the SQL review will pass and it will roll out automatically. The issue will become &lt;code&gt;Done&lt;/code&gt; as well.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-done.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-issue-done.webp" alt="bb-issue-done" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may ask why it's rolling out automatically, it's because for &lt;strong&gt;Community Plan&lt;/strong&gt;, the rollout policy is automatic if the SQL review passes. You may go to &lt;strong&gt;Environments&lt;/strong&gt; to check.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-env-automatic.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Ffirst-schema-change%2Fbb-env-automatic.webp" alt="bb-env-automatic" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Step
&lt;/h2&gt;

&lt;p&gt;Now you have successfully performed your first schema change in Bytebase. It's the core part of Bytebase. You can continue to dig deeper by following &lt;a href="https://www.bytebase.com/docs/tutorials/deploy-schema-migration/" rel="noopener noreferrer"&gt;Deploy Schema Migration with Rollout Policy&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>database</category>
      <category>schema</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>🔥Top psql Commands with Examples🐘</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Wed, 22 Nov 2023 03:10:10 +0000</pubDate>
      <link>https://dev.to/bytebase/top-psql-commands-with-examples-1h5</link>
      <guid>https://dev.to/bytebase/top-psql-commands-with-examples-1h5</guid>
      <description>&lt;p&gt;PostgreSQL, or Postgres, is a free and open-source relational database management system (RDBMS) known for its reliability, feature robustness, and performance. It has been actively developed for over 35 years and is considered one of the most advanced open-source RDBMSs available.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;psql&lt;/code&gt; is the official CLI client to command the PostgreSQL database. It supports interactive and noninteractive use.&lt;/p&gt;

&lt;p&gt;Below list of the 10 most commonly used &lt;code&gt;psql&lt;/code&gt; commands with examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Connect to a database - &lt;code&gt;psql -U xxx -p -h xxx -d xxx&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you are using the default PostgreSQL username &lt;code&gt;postgres&lt;/code&gt; and have not set a password, you can connect to the database using the following command. You will be prompted to enter the password for the &lt;code&gt;postgres&lt;/code&gt; user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are using a different username or have set a password for the &lt;code&gt;postgres&lt;/code&gt; user, you can connect to the database using the following command. You will be prompted to enter the password for the specified user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -U your_username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the PostgreSQL server is running on a different port than the default (5432), you can connect to the database using the following command. You will be prompted to enter the password for the specified user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -p 5433
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the PostgreSQL server is running on a remote host, you can connect to the database using the following command. You will be prompted to enter the password for the specified user.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -h remote_host -p 5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In all of these examples, you can also specify the name of the database you want to connect to using the -d option. For example, to connect to the database named my_database, you would use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -d my_database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Run a single command - &lt;code&gt;-c&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To run a single command in PostgreSQL using the &lt;code&gt;psql&lt;/code&gt; CLI, you can use the &lt;code&gt;-c&lt;/code&gt; option followed by the SQL command you want to execute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql -d my_database -c "SELECT * FROM my_table;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;List available databases - &lt;code&gt;\l&lt;/code&gt;
This command will all of the databases that are currently available on the server. This can be useful for getting an overview of the databases that are available to you.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \l

   Name    |  Owner   | Encoding | Locale Provider |  Collate   |   Ctype    | ICU Locale | ICU Rules |   Access privileges
-----------+----------+----------+-----------------+------------+------------+------------+-----------+-----------------------
 postgres  | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           |
 template0 | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           | =c/postgres          +
           |          |          |                 |            |            |            |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | en_US.utf8 | en_US.utf8 |            |           | =c/postgres          +
           |          |          |                 |            |            |            |           | postgres=CTc/postgres
(3 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Switch to a specified database - &lt;code&gt;\c&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;\c&lt;/code&gt; command switches to the specified database. This is useful for working with a specific database after you have listed the available databases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \c postgres


psql (16.0, server 16.1 (Debian 16.1-1.pgdg120+1))
You are now connected to database "postgres" as user "postgres".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. List tables - &lt;code&gt;\dt&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;\dt&lt;/code&gt; command lists all of the tables that are currently available in the current database. This can be useful for getting an overview of the tables that are available to you in the current database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \dt

List of relations
Schema | Name | Type | Owner
-------+---------+-------+--------
public | mytable | table | postgres
public | users | table | postgres
public | orders | table | postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Describe a specified table - &lt;code&gt;\d&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;\d&lt;/code&gt; command describes the specified table. This provides information about the table's columns, data types, and constraints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \d my_table

Table: my_table

Column | Data Type | Constraints
-------+------------+-------------------
id | SERIAL | PRIMARY KEY
name | VARCHAR(50) | NOT NULL
email | VARCHAR(100) | UNIQUE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Listing users - &lt;code&gt;\du&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;\du&lt;/code&gt; command lists all of the users that exist in the current database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \du

List of roles
Role name | Attributes
---------+------------
postgres | SUPERUSER, CREATEDB, CREATEROLE, INHERIT, REPLICATION
myuser | CREATEDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;\du username&lt;/code&gt; command is used to list the roles (users) in the current database that have the specified username.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \du myuser

List of roles
Role name | Attributes
---------+------------
myuser | CREATEDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the specified username does not match any roles in the current database, the output will be an empty table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# \du youruser

List of roles
Role name | Attributes
---------+------------
 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Show connections - &lt;code&gt;pg_stat_activity&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To list all active connections, execute the following SQL query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# SELECT * FROM pg_stat_activity;

pid | datname | username | client_addr | client_port | backend_start | query_start | query | state
-------+---------+---------+-------------+-------------+---------------+---------------+-----------------+----------
1234 | mydb1   | postgres | 192.168.1.100 | 5432 | 2023-10-04 15:04:00 | 2023-10-04 15:04:05 | SELECT * FROM mytable; | active
5678 | mydb2   | user1    | 192.168.1.101 | 5432 | 2023-10-04 15:05:00 | 2023-10-04 15:05:03 | UPDATE mytable SET name = 'John Doe' WHERE id = 123; | idle in transaction
9012 | postgres | 192.168.1.102 | 5432 | 2023-10-04 15:06:00 |                |                | idle |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also filter the results to specific connections based on criteria such as username, database name, or state. For example, to list only active connections to the database &lt;code&gt;mydb&lt;/code&gt;, use the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# SELECT * FROM pg_stat_activity WHERE datname = 'mydb' AND state = 'active';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, to list connections currently executing queries, use the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# SELECT * FROM pg_stat_activity WHERE state IN ('active', 'idle in transaction');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  9. Kill connection - &lt;code&gt;pg_terminate_backend(), kill&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Once you have identified the PID of the connection you want to kill, you can use the &lt;code&gt;pg_terminate_backend()&lt;/code&gt; function to terminate it. For example, the following command will kill the connection with PID 1234:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres-# SELECT pg_terminate_backend(1234);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the kill command is a less common method for killing PostgreSQL connections, as it can be more dangerous and less reliable than using the pg_terminate_backend() function. &lt;/p&gt;

&lt;p&gt;However, it may be necessary in certain situations, such as when the connection is unresponsive or the &lt;code&gt;pg_terminate_backend()&lt;/code&gt; function fails.&lt;/p&gt;

&lt;p&gt;kill -9 1234&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


## 10. Quit - `\q`,Ctrl+D/Ctrl+Z
Using the `\q` command or the keyboard shortcut Ctrl+D(or Ctrl+Z on Windows). This will immediately exit the `psql `CLI.👋
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>🚛 Deploy Database Schema Migrations with Bytebase</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Fri, 10 Nov 2023 07:09:31 +0000</pubDate>
      <link>https://dev.to/bytebase/deploy-schema-migration-with-bytebase-1173</link>
      <guid>https://dev.to/bytebase/deploy-schema-migration-with-bytebase-1173</guid>
      <description>&lt;p&gt;&lt;a href="https://www.bytebase.com/" rel="noopener noreferrer"&gt;Bytebase&lt;/a&gt; offers a powerful GUI for schema migration deployments. This tutorial will show you how to use Bytebase to deploy schema migrations with features like SQL Review, custom approval, time scheduling, and more.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fgraph-4-steps.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fgraph-4-steps.webp" alt="graph-4-steps" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bytebase offers &lt;strong&gt;Community&lt;/strong&gt;, &lt;strong&gt;Pro&lt;/strong&gt; and &lt;strong&gt;Enterprise&lt;/strong&gt; &lt;a href="https://www.bytebase.com/pricing" rel="noopener noreferrer"&gt;Plans&lt;/a&gt;. Advanced plans&lt;br&gt;
unlock new capabilities of deploying schema migrations and this tutorial will walk you through them progressively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features covered
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Level 1 - &lt;a href="https://www.bytebase.com/docs/sql-review/overview/" rel="noopener noreferrer"&gt;Automatic SQL Review&lt;/a&gt; (available in all plans)&lt;/li&gt;
&lt;li&gt;Level 2 - &lt;a href="https://www.bytebase.com/docs/administration/environment-policy/rollout-policy/" rel="noopener noreferrer"&gt;Rollout Policy&lt;/a&gt; (available in Pro and Enterprise)&lt;/li&gt;
&lt;li&gt;Level 3 - &lt;a href="https://www.bytebase.com/docs/administration/custom-approval/" rel="noopener noreferrer"&gt;Custom Approval&lt;/a&gt; (available in Enterprise)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Make sure your Docker is running, and start the Bytebase Docker container with the following command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; bytebase &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--publish&lt;/span&gt; 5678:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-cmd&lt;/span&gt; &lt;span class="s2"&gt;"curl --fail http://localhost:5678/healthz || exit 1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-interval&lt;/span&gt; 5m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-timeout&lt;/span&gt; 60s &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume&lt;/span&gt; ~/.bytebase/data:/var/opt/bytebase &lt;span class="se"&gt;\&lt;/span&gt;
  bytebase/bytebase:%%bb_version%% &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; /var/opt/bytebase &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Bytebase is running successfully in Docker, and you can visit it via &lt;code&gt;localhost:5678&lt;/code&gt;. Register an admin account and it will be granted the &lt;code&gt;workspace owner&lt;/code&gt; role automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Level 1: Automatic rollout with SQL review (Community Plan)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Environments&lt;/strong&gt; &amp;gt; &lt;strong&gt;Test&lt;/strong&gt; and &lt;strong&gt;Environments&lt;/strong&gt; &amp;gt; &lt;strong&gt;Prod&lt;/strong&gt;, you'll see

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Rollout policy&lt;/code&gt; is &lt;code&gt;automatic&lt;/code&gt;. &lt;strong&gt;Unless there's some warning or error, the rollout will be automatically executed after the issue is created&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SQL Review&lt;/code&gt; is enabled on &lt;code&gt;Prod&lt;/code&gt; with a sample policy.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-env-rollout-automatic-test-prod.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-env-rollout-automatic-test-prod.webp" alt="bb-env-rollout-automatic-test-prod" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;code&gt;SQL Review Sample Policy&lt;/code&gt; to go to SQL Review policy, there're three rules activated. Let's pay attention to &lt;code&gt;Enforce NOT NULL constraints on columns&lt;/code&gt; rule, and we'll try to violate it.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-sql-review-sample-policy.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-sql-review-sample-policy.webp" alt="bb-sql-review-sample-policy" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Sample Project&lt;/code&gt;, click &lt;strong&gt;Edit Schema&lt;/strong&gt;, choose both &lt;code&gt;Test&lt;/code&gt; and &lt;code&gt;Prod&lt;/code&gt; databases, and click &lt;strong&gt;Next&lt;/strong&gt;. Paste the following SQL statements into &lt;strong&gt;Raw SQL&lt;/strong&gt;, and click &lt;strong&gt;Preview issue&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"employee"&lt;/span&gt;
    &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="nv"&gt;"country"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;SQL Review checks will dry run before the issue is created. Here let's create the issue regardless of the dry run result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the issue is created, SQL Review will run automatically along with some other checks. You'll see there's a warning for the task on &lt;code&gt;Prod&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-warning-prod.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-warning-prod.webp" alt="bb-issue-warning-prod" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-sql-review-warning.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-sql-review-warning.webp" alt="bb-issue-sql-review-warning" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Edit&lt;/strong&gt;, paste this SQL statement and click &lt;strong&gt;Save&lt;/strong&gt;. Apply this change to all tasks. The SQL checks will run again, and you'll see the warning disappear and it will roll out automatically.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"employee"&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="nv"&gt;"country"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-done-free.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-done-free.webp" alt="bb-issue-done-free" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;View change&lt;/strong&gt; to see the diff or go to &lt;strong&gt;Change History&lt;/strong&gt; to view all changes.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-done-show-diff.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-done-show-diff.webp" alt="bb-issue-done-show-diff" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-proj-change-list.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-proj-change-list.webp" alt="bb-proj-change-list" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 2: Manual rollout with dedicated roles and scheduled time (Pro Plan)
&lt;/h3&gt;

&lt;p&gt;With Pro Plan, you'll get two additional features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual rollout policy. You can specify multiple pre-defined roles to manually roll out the change.&lt;/li&gt;
&lt;li&gt;Time scheduling. You can specify a particular time to roll out the change.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To simplify the process, we'll use 14-day enterprise trial here. Click the &lt;strong&gt;Start free trial&lt;/strong&gt; to upgrade.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Environments&lt;/strong&gt; &amp;gt; &lt;strong&gt;Prod&lt;/strong&gt;, choose &lt;code&gt;Manual rollout by dedicated roles&lt;/code&gt; and check all the roles. Click &lt;strong&gt;Update&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-env-prod-manual.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-env-prod-manual.webp" alt="bb-env-prod-manual" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Sample Project&lt;/code&gt;, click &lt;strong&gt;Edit Schema&lt;/strong&gt;, choose both &lt;code&gt;Test&lt;/code&gt; and &lt;code&gt;Prod&lt;/code&gt; databases, and click &lt;strong&gt;Next&lt;/strong&gt;. Paste the following SQL statements into &lt;strong&gt;Raw SQL&lt;/strong&gt;, and click &lt;strong&gt;Preview issue&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"employee"&lt;/span&gt;
    &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="nv"&gt;"city"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Create&lt;/strong&gt;, and after &lt;strong&gt;Task checks&lt;/strong&gt; runs, you'll see the SQL running on &lt;code&gt;Test&lt;/code&gt; automatically but waiting to run on &lt;code&gt;Prod&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-prod-waiting.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-prod-waiting.webp" alt="bb-issue-prod-waiting" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Rollout&lt;/strong&gt; to trigger directly or set a &lt;strong&gt;Rollout time&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-proj-set-rollout-time.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-proj-set-rollout-time.webp" alt="bb-proj-set-rollout-time" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 3: Manual rollout with custom approval (Enterprise Plan)
&lt;/h3&gt;

&lt;p&gt;If you want the approval flow to be more dynamic based on the context like the type of SQL statements, the affected rows and etc,&lt;br&gt;
then you can configure &lt;a href="https://www.bytebase.com/docs/administration/custom-approval/" rel="noopener noreferrer"&gt;custom approval flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;Instances&lt;/strong&gt; and click &lt;strong&gt;Assign License&lt;/strong&gt; for both instances. Without doing this, the enterprise plan required for custom approval won't be enabled on instances.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Settings&lt;/strong&gt; (the gear icon) &amp;gt; &lt;strong&gt;Security &amp;amp; Policy&lt;/strong&gt; &amp;gt; &lt;strong&gt;Custom Approval&lt;/strong&gt;. Choose &lt;code&gt;Project Owner -&amp;gt; DBA&lt;/code&gt; as High Risk for DDL.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-custom-approval.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-custom-approval.webp" alt="bb-custom-approval" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;the related risk rules&lt;/strong&gt; or &lt;strong&gt;Settings&lt;/strong&gt; (the gear icon) &amp;gt; &lt;strong&gt;Security &amp;amp; Policy&lt;/strong&gt; &amp;gt; &lt;strong&gt;Risk Center&lt;/strong&gt;. Click &lt;strong&gt;Add rule&lt;/strong&gt;. Set &lt;code&gt;High&lt;/code&gt; Risk and &lt;code&gt;DDL&lt;/code&gt; as &lt;code&gt;The risk for the production environment is considered to be high.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-risk-center-add-rule.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-risk-center-add-rule.webp" alt="bb-risk-center-add-rule" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Settings&lt;/strong&gt; (the gear icon) and add a DBA account. Click it in the &lt;strong&gt;Active members&lt;/strong&gt; list, and edit its password. You'll need this account later to do the approval.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Environments&lt;/strong&gt; &amp;gt; &lt;strong&gt;Prod&lt;/strong&gt;, you now unlock the third option for rollout policy &lt;code&gt;Manual rollout by the last approver from the custom approval flow&lt;/code&gt;. Choose it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-env-prod-manual-approval.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-env-prod-manual-approval.webp" alt="bb-env-prod-manual-approval" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Sample Project&lt;/code&gt;, click &lt;strong&gt;Edit Schema&lt;/strong&gt;, choose both &lt;code&gt;Test&lt;/code&gt; and &lt;code&gt;Prod&lt;/code&gt; databases, and click &lt;strong&gt;Next&lt;/strong&gt;. Paste the following SQL statements into &lt;strong&gt;Raw SQL&lt;/strong&gt;, and click &lt;strong&gt;Preview issue&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;"employee"&lt;/span&gt;
    &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="nv"&gt;"district"&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create the issue and the approval flow is matched. Since it's in the pipeline, it will be brought forward to the &lt;code&gt;Test&lt;/code&gt; stage to review earlier. Follow its order to approve. &lt;code&gt;DBA&lt;/code&gt; will be the one to do the rollout.&lt;/li&gt;
&lt;/ol&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-custom-approval-waiting.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fdeploy-schema-migration%2Fbb-issue-custom-approval-waiting.webp" alt="bb-issue-custom-approval-waiting" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;You have now learned how to use Bytebase to deploy schema migration in a basic way. Bytebase also provides other advanced features for your interests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.bytebase.com/docs/vcs-integration/overview/" rel="noopener noreferrer"&gt;GitOps&lt;/a&gt; - Observe Git code push events and trigger schema migration;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.bytebase.com/docs/change-database/batch-change/" rel="noopener noreferrer"&gt;Batch changes&lt;/a&gt; - Change multiple databases in a single workflow;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.bytebase.com/docs/branching/" rel="noopener noreferrer"&gt;Branching&lt;/a&gt; - Collaborate on schema changes using Git-like branching;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.bytebase.com/docs/changelist/" rel="noopener noreferrer"&gt;Changelist&lt;/a&gt; - Organize and apply changes sequentially, or export them for offline execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Join our &lt;a href="https://discord.com/invite/huyw7gRsyA" rel="noopener noreferrer"&gt;Discord channel&lt;/a&gt; to discuss.&lt;/p&gt;

</description>
      <category>database</category>
      <category>devops</category>
      <category>sql</category>
      <category>schema</category>
    </item>
    <item>
      <title>🐣 Step-by-Step Guide to Data Masking 🎭</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Fri, 13 Oct 2023 09:50:51 +0000</pubDate>
      <link>https://dev.to/bytebase/step-by-step-guide-to-data-masking-1h4l</link>
      <guid>https://dev.to/bytebase/step-by-step-guide-to-data-masking-1h4l</guid>
      <description>&lt;p&gt;Bytebase &lt;strong&gt;Dynamic Data Masking&lt;/strong&gt; can mask sensitive data in the query result based on the context on the fly.&lt;br&gt;
It helps organizations to &lt;strong&gt;protect sensitive data&lt;/strong&gt; from being exposed to unauthorized users.&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Fsecurity%2Fdata-masking%2Fbb-masking-overview.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Fsecurity%2Fdata-masking%2Fbb-masking-overview.webp" alt="bb-masking-overview" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This tutorial will teach you how to set up and try out data masking in Bytebase within 30 mins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before starting, make sure you have installed &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Make sure your Docker is running, and start the Bytebase Docker container with following command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; bytebase &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--publish&lt;/span&gt; 5678:8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-cmd&lt;/span&gt; &lt;span class="s2"&gt;"curl --fail http://localhost:5678/healthz || exit 1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-interval&lt;/span&gt; 5m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-timeout&lt;/span&gt; 60s &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume&lt;/span&gt; ~/.bytebase/data:/var/opt/bytebase &lt;span class="se"&gt;\&lt;/span&gt;
  bytebase/bytebase:%%bb_version%% &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; /var/opt/bytebase &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Bytebase is running successfully in Docker, and you can visit it via &lt;code&gt;localhost:5678&lt;/code&gt;. Register an admin account and it will be granted the &lt;code&gt;workspace owner&lt;/code&gt; role automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go back to the Bytebase console, click &lt;strong&gt;Start free trial&lt;/strong&gt; on the left bottom and upgrade to &lt;strong&gt;Enterprise plan&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Instances&lt;/strong&gt;, and click &lt;strong&gt;Assign License&lt;/strong&gt;. Select both instances, and click &lt;strong&gt;Confirm&lt;/strong&gt;. Without doing this, the enterprise plan required for data masking won't be enabled on instances.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  No Masking
&lt;/h3&gt;

&lt;p&gt;Login to Bytebase,click &lt;strong&gt;SQL Editor&lt;/strong&gt;, you'll be redirected to SQL Editor page. Choose &lt;code&gt;(Prod) employee&lt;/code&gt; &amp;gt; &lt;code&gt;public&lt;/code&gt; &amp;gt; &lt;code&gt;employee&lt;/code&gt;, and then run &lt;code&gt;SELECT * FROM employee;&lt;/code&gt;, you'll see the following result. Run the same query against &lt;code&gt;(Test) employee&lt;/code&gt;, the result is the same.&lt;br&gt;
   &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-prod.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-prod.webp" alt="bb-sql-editor-query-employee-prod" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-test.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-test.webp" alt="bb-sql-editor-query-employee-test" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Masking Rule
&lt;/h3&gt;

&lt;p&gt;You may want to batch apply masking settings, for example, you want to mask all the &lt;code&gt;birth_date&lt;/code&gt; columns in all the tables in the &lt;code&gt;employee&lt;/code&gt; database. You can use &lt;strong&gt;Global Masking Rule&lt;/strong&gt; to achieve this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;Setting icon&lt;/strong&gt; on the top right. Click &lt;strong&gt;Security &amp;amp; Policy&lt;/strong&gt; &amp;gt; &lt;strong&gt;Data Masking&lt;/strong&gt;. Click &lt;strong&gt;Global Masking Rule&lt;/strong&gt; and then &lt;strong&gt;Add rule&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name the rule as &lt;code&gt;birth_date should be masked&lt;/code&gt;, and select &lt;code&gt;Column name&lt;/code&gt;, &lt;code&gt;==&lt;/code&gt;. Fill &lt;code&gt;birth_date&lt;/code&gt; in the input box, and click &lt;strong&gt;Confirm&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-data-masking-global-birth-date.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-data-masking-global-birth-date.webp" alt="bb-data-masking-global-birth-date" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go back to the SQL Editor page, Choose &lt;code&gt;(Prod) employee&lt;/code&gt; &amp;gt; &lt;code&gt;public&lt;/code&gt; &amp;gt; &lt;code&gt;employee&lt;/code&gt; and run &lt;code&gt;SELECT * FROM employee;&lt;/code&gt; again. You'll see the &lt;code&gt;birth_date&lt;/code&gt; is masked. Choose &lt;code&gt;(Test) employee&lt;/code&gt;, the result is the same.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-prod-masked.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-prod-masked.webp" alt="bb-sql-editor-query-employee-prod-masked" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-test-masked.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-employee-test-masked.webp" alt="bb-sql-editor-query-employee-test-masked" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Column Masking Rule
&lt;/h3&gt;

&lt;p&gt;If you want to mask a specific column in a specific table, you can use &lt;strong&gt;Column Masking Rule&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Databases&lt;/strong&gt; and choose &lt;code&gt;employee&lt;/code&gt; on &lt;code&gt;Prod&lt;/code&gt;, and select &lt;code&gt;salary&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the edit(pen) icon on the &lt;code&gt;amount&lt;/code&gt; row, and click &lt;strong&gt;Full&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-database-table-amount.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-database-table-amount.webp" alt="bb-database-table-amount" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go back to the SQL Editor page, Choose &lt;code&gt;(Prod) employee&lt;/code&gt; &amp;gt; &lt;code&gt;public&lt;/code&gt; &amp;gt; &lt;code&gt;salary&lt;/code&gt; and run &lt;code&gt;SELECT * FROM salary;&lt;/code&gt; again. You'll see the &lt;code&gt;amount&lt;/code&gt; is masked. Choose &lt;code&gt;(Test) employee&lt;/code&gt;, it's not.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-salary-prod-masked.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-salary-prod-masked.webp" alt="bb-sql-editor-query-salary-prod-masked" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-salary-test.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-salary-test.webp" alt="bb-sql-editor-query-salary-test" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Grant unmasked access to a user
&lt;/h3&gt;

&lt;p&gt;What if you want to reveal the masked data to a specific user? You can grant unmasked access.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Settings icon&lt;/strong&gt; on the top right, and click &lt;strong&gt;Members&lt;/strong&gt;. Add a &lt;code&gt;DBA&lt;/code&gt; user and click &lt;strong&gt;+ Add&lt;/strong&gt;. Click its link in the &lt;strong&gt;Active members&lt;/strong&gt; section, click &lt;strong&gt;Edit&lt;/strong&gt; to set a password, click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Databases&lt;/strong&gt; and choose &lt;code&gt;employee&lt;/code&gt; on &lt;code&gt;Prod&lt;/code&gt;, and select &lt;code&gt;salary&lt;/code&gt; table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the &lt;strong&gt;edit(pen) icon&lt;/strong&gt; on the &lt;code&gt;amount&lt;/code&gt; row, and click &lt;strong&gt;Grant Access&lt;/strong&gt;. Select the &lt;code&gt;DBA&lt;/code&gt; user, and click &lt;strong&gt;Confirm&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-database-table-amount-grant-access.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-database-table-amount-grant-access.webp" alt="bb-database-table-amount-grant-access" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Login as the &lt;code&gt;DBA&lt;/code&gt; user, go to SQL Editor, choose &lt;code&gt;(Prod)Employee&lt;/code&gt; &amp;gt; &lt;code&gt;public&lt;/code&gt; &amp;gt; &lt;code&gt;salary&lt;/code&gt; and run &lt;code&gt;SELECT * FROM salary;&lt;/code&gt; again. You'll see the &lt;code&gt;amount&lt;/code&gt; is not masked.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-salary-prod-not-masked.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fstep-by-step-guide-to-data-masking%2Fbb-sql-editor-query-salary-prod-not-masked.webp" alt="bb-sql-editor-query-salary-prod-not-masked" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You can now mask data in Bytebase using two methods: use &lt;strong&gt;Global Masking Rule&lt;/strong&gt; to batch apply masking settings, and use &lt;strong&gt;Column Masking Rule&lt;/strong&gt; to mask a specific column in a specific table. You can also grant access to a specific user to reveal the masked data.&lt;/p&gt;

&lt;p&gt;Bytebase supports &lt;a href="https://www.bytebase.com/docs/introduction/supported-databases/" rel="noopener noreferrer"&gt;Data Masking for different databases&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have any questions, please &lt;a href="https://discord.com/invite/huyw7gRsyA" rel="noopener noreferrer"&gt;join our discord&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>database</category>
      <category>mysql</category>
      <category>postgres</category>
    </item>
    <item>
      <title>How to Manage Data Access for Developers</title>
      <dc:creator>Adela</dc:creator>
      <pubDate>Tue, 05 Sep 2023 03:15:22 +0000</pubDate>
      <link>https://dev.to/bytebase/how-to-manage-data-access-for-developers-30of</link>
      <guid>https://dev.to/bytebase/how-to-manage-data-access-for-developers-30of</guid>
      <description>&lt;p&gt;When you have a team of developers, it is critical to restrict their access to data to only what they need to do their job. This ensures to protect sensitive data and prevent unauthorized access.&lt;/p&gt;

&lt;p&gt;Bytebase provides several powerful features to help you achieve this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can restrict users to only view data in a specific environment.&lt;/li&gt;
&lt;li&gt;Developer can require approval to access a specific table.&lt;/li&gt;
&lt;li&gt;You can mask sensitive data, such as salary.&lt;/li&gt;
&lt;li&gt;You can track who has accessed which data.&lt;/li&gt;
&lt;li&gt;You can add watermarks to your data to deter unauthorized copying.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial will walk you through how to use these features in Bytebase.&lt;/p&gt;

&lt;p&gt;All are &lt;strong&gt;Enterprise Plan&lt;/strong&gt; only features. However, you can start a &lt;strong&gt;14-day trial of the Enterprise Plan&lt;/strong&gt; with one click without providing additional information (no credit card required).&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparation
&lt;/h2&gt;

&lt;p&gt;Make sure you have &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; installed, and if you don’t have important existing Bytebase data locally, you can start over from scratch by &lt;code&gt;rm -rf ~/.bytebase/data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You’ll need two Bytebase accounts –&amp;nbsp; one &lt;strong&gt;Owner&lt;/strong&gt; and one &lt;strong&gt;Developer&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner&lt;/strong&gt; is the one who configures the settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer&lt;/strong&gt; is the one who should only see information based on the configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1 - Run Bytebase via Docker
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Make sure your docker daemon is running, and then start the Bytebase docker container by typing the following command in the terminal.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--init&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--name&lt;/span&gt; bytebase &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--restart&lt;/span&gt; always &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--publish&lt;/span&gt; 5678:8080 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--volume&lt;/span&gt; ~/.bytebase/data:/var/opt/bytebase &lt;span class="se"&gt;\&lt;/span&gt;
bytebase/bytebase:2.7.0 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; /var/opt/bytebase &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--port&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2 - Register accounts Owner and Developer
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Visit &lt;code&gt;localhost:5678&lt;/code&gt; in the browser. Register an admin account, we’ll refer to it as &lt;strong&gt;Owner&lt;/strong&gt;. This account will be granted &lt;code&gt;Workspace Owner&lt;/code&gt; role. To keep it simple, we use this &lt;strong&gt;Owner&lt;/strong&gt; instead of registerating another &lt;strong&gt;DBA&lt;/strong&gt; in this tutorial. Check &lt;a href="https://www.bytebase.com/docs/concepts/roles-and-permissions" rel="noopener noreferrer"&gt;Roles and Permissions (RBAC)&lt;/a&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-admin-register.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-admin-register.webp" alt="admin-register" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log out and register another normal account  - we’ll refer to it as &lt;strong&gt;Developer&lt;/strong&gt;. This account will be granted &lt;code&gt;Workspace Developer&lt;/code&gt; role.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-normal-register.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-normal-register.webp" alt="register" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switch back to &lt;strong&gt;Owner&lt;/strong&gt;, now you can see there's a &lt;code&gt;Sample Project&lt;/code&gt; with two embedded databases both named &lt;code&gt;employee&lt;/code&gt; mapping to &lt;code&gt;Test&lt;/code&gt; and &lt;code&gt;Prod&lt;/code&gt; environments respectively.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-proj-db.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-proj-db.webp" alt="bb-proj-db" width="800" height="526"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Members&lt;/strong&gt; and &lt;strong&gt;Grant Access&lt;/strong&gt; to add &lt;strong&gt;Developer&lt;/strong&gt; as &lt;code&gt;Developer&lt;/code&gt;. Only after this, can he see the project.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-proj-members-dev.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-proj-members-dev.webp" alt="bb-proj-members-dev" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3 - Start Enterprise Plan trial and assign license to instances
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Start free trial&lt;/strong&gt; on the left bottom. Click &lt;strong&gt;Start 14 days trial (no credit card required)&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Instances&lt;/strong&gt; on the top navigation bar. You can see there are two instances. Click them one by one, and turn on &lt;strong&gt;Assign License&lt;/strong&gt; for both. You may check &lt;a href="https://www.bytebase.com/docs/administration/license/" rel="noopener noreferrer"&gt;Manage License&lt;/a&gt; for more details.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-instance-assign-license.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-instance-assign-license.webp" alt="bb-instance-assign-license" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Data Access Control - Query Data
&lt;/h2&gt;

&lt;p&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt;, and you can see &lt;code&gt;Sample Project&lt;/code&gt;. Click &lt;strong&gt;SQL Editor&lt;/strong&gt; on the left bar. Within SQL Editor, you can't see anything. It's because you don't have any database access permission yet.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-sql-editor-blank.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-sql-editor-blank.webp" alt="bb-sql-editor-blank" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 - Developer can skip approvals to query data
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Owner&lt;/strong&gt;, and go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Data Access Control&lt;/strong&gt;. Check &lt;code&gt;Skip approval&lt;/code&gt; for &lt;strong&gt;Query Data&lt;/strong&gt; in &lt;code&gt;Test&lt;/code&gt; environment.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-dac-skip.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-dac-skip.webp" alt="bb-owner-dac-skip" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt; and go into SQL Editor again. Now you can see database under &lt;code&gt;Test&lt;/code&gt; environment. Select &lt;code&gt;(Prod)employee&lt;/code&gt;, input &lt;code&gt;SELECT * FROM salary;&lt;/code&gt; and run and you can see the result. Change &lt;code&gt;salary&lt;/code&gt; to any other tables and run, you can see data as well.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-dev-sql-editor-test-select-salary.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-dev-sql-editor-test-select-salary.webp" alt="bb-dev-sql-editor-test-select-salary" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2 - Developer can request to query data
&lt;/h3&gt;

&lt;p&gt;In practice, developers can access any database on &lt;code&gt;Test&lt;/code&gt; environment, but they need to obtain permission to access &lt;code&gt;Prod&lt;/code&gt; environment. In this step, we'll show you how to implement this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Owner&lt;/strong&gt;, and go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Custom Approval&lt;/strong&gt;. Scroll down to &lt;strong&gt;Request Query&lt;/strong&gt;, and choose &lt;code&gt;Workspace Owner&lt;/code&gt; as &lt;strong&gt;Approval flow&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Risk Center&lt;/strong&gt;. Click &lt;strong&gt;Add Rule&lt;/strong&gt;. Choose &lt;code&gt;Request Query&lt;/code&gt; as &lt;strong&gt;Type&lt;/strong&gt;. Choose &lt;code&gt;High&lt;/code&gt; as &lt;strong&gt;Risk&lt;/strong&gt;. Click &lt;strong&gt;Load&lt;/strong&gt; for the first template on the right. Click &lt;strong&gt;Add&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-risk-center-request-query.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-risk-center-request-query.webp" alt="bb-owner-risk-center-request-query" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt;, and go to &lt;code&gt;Sample Project&lt;/code&gt;, click &lt;strong&gt;Request Query&lt;/strong&gt;. Choose &lt;strong&gt;Manual select&lt;/strong&gt; and then &lt;code&gt;employee&lt;/code&gt; table under &lt;code&gt;Prod&lt;/code&gt; environment. Click &lt;strong&gt;OK&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-proj-request-query-salary.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-proj-request-query-salary.webp" alt="bb-proj-request-query-salary" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A request issue is created with the approval flow &lt;code&gt;Workspace Owner&lt;/code&gt; we just defined.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-issue-request.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-issue-request.webp" alt="bb-issue-request" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Owner&lt;/strong&gt;, go to this issue and click &lt;strong&gt;Approve&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt;, and go to SQL Editor. You can see &lt;code&gt;salary&lt;/code&gt; table under &lt;code&gt;Prod&lt;/code&gt; environment. Select &lt;code&gt;(Prod)employee&lt;/code&gt;, input &lt;code&gt;SELECT * FROM salary;&lt;/code&gt; and run, you'll see the result.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-salary-before-masking.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-salary-before-masking.webp" alt="bb-salary-before-masking" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Input &lt;code&gt;SELECT * FROM employee;&lt;/code&gt; and run, it'll show permission denied. You can click &lt;strong&gt;Request Query&lt;/strong&gt; to request permission.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 3 - Owner can grant access to query data directly
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in as &lt;strong&gt;Owner&lt;/strong&gt;, and go to &lt;code&gt;Sample Project&lt;/code&gt;. Click &lt;strong&gt;Members&lt;/strong&gt; and then &lt;strong&gt;Grant Access&lt;/strong&gt;. Choose &lt;code&gt;Developer&lt;/code&gt; and assign the role &lt;code&gt;Querier&lt;/code&gt;, &lt;code&gt;All&lt;/code&gt; for &lt;strong&gt;Databases&lt;/strong&gt;. Click &lt;strong&gt;Confirm&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt;, and go to SQL Editor. You can see all databases under &lt;code&gt;Test&lt;/code&gt; and &lt;code&gt;Prod&lt;/code&gt; environment. Select &lt;code&gt;(Prod)employee&lt;/code&gt;, input &lt;code&gt;SELECT * FROM employee;&lt;/code&gt; and run, you can see the result. Change &lt;code&gt;employee&lt;/code&gt; to any other tables and run, you can see data as well.
## Data Access Control - Export Data
Export Data is similar to Query Data. &lt;strong&gt;Developer&lt;/strong&gt; can skip approvals, request approvals to export data or be granted &lt;code&gt;Exporter&lt;/code&gt; role directly by &lt;strong&gt;Owner&lt;/strong&gt;.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-export-request.webp" alt="bb-export-request" width="800" height="415"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Data Masking
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt;, and go to SQL Editor. Select &lt;code&gt;(Prod)employee&lt;/code&gt;, input &lt;code&gt;SELECT * FROM salary;&lt;/code&gt; and run, you can see all the information. We want to mask the &lt;code&gt;amount&lt;/code&gt; column.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Login in as &lt;strong&gt;Owner&lt;/strong&gt;, click &lt;strong&gt;Databases&lt;/strong&gt; on the top navigation bar and then choose &lt;code&gt;employee&lt;/code&gt; under &lt;code&gt;Prod&lt;/code&gt; environment. Scroll down to &lt;strong&gt;Tables&lt;/strong&gt;, click &lt;code&gt;salary&lt;/code&gt; table, and check &lt;code&gt;amount&lt;/code&gt; row as &lt;strong&gt;Sensitive&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-masking-salary.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-masking-salary.webp" alt="bb-owner-masking-salary" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in as &lt;strong&gt;Developer&lt;/strong&gt;, and go to SQL Editor. Select &lt;code&gt;(Prod)employee&lt;/code&gt;, input &lt;code&gt;SELECT * FROM salary;&lt;/code&gt; and run, you can see the &lt;code&gt;amount&lt;/code&gt; column is masked.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-masking-salary.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-masking-salary.webp" alt="bb-masking-salary" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Audit Log
&lt;/h2&gt;

&lt;p&gt;Bytebase records everything happened within the system. You may check the audit log.&lt;br&gt;
Log in as &lt;strong&gt;Owner&lt;/strong&gt;, and go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;Audit Log&lt;/strong&gt;. You can see all the queries you've run.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-audit-log.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-owner-audit-log.webp" alt="bb-owner-audit-log" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Watermark
&lt;/h2&gt;

&lt;p&gt;Bytebase provides watermark feature to help you identify the data source. You can click &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;General&lt;/strong&gt;, and scroll to &lt;strong&gt;Security&lt;/strong&gt; to enable it.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-watermark.webp" 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%2Fwww.bytebase.com%2Fcontent%2Fdocs%2Ftutorials%2Fhow-to-manage-data-access-for-developers%2Fbb-watermark.webp" alt="bb-watermark" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;With Bytebase, you have now tried out the basic management of data access for developers. If you want to know more about database change as well, you can check &lt;a href="https://www.bytebase.com/docs/tutorials/database-change-management-with-postgresql/" rel="noopener noreferrer"&gt;DevOps: Database Change Management with PostgreSQL&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>developer</category>
      <category>database</category>
      <category>data</category>
      <category>security</category>
    </item>
  </channel>
</rss>
