<?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: Quyền</title>
    <description>The latest articles on DEV Community by Quyền (@phuthuycoding).</description>
    <link>https://dev.to/phuthuycoding</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%2F2837963%2F50411345-be97-4eb9-9e87-e551ee54b1bf.png</url>
      <title>DEV Community: Quyền</title>
      <link>https://dev.to/phuthuycoding</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/phuthuycoding"/>
    <language>en</language>
    <item>
      <title>Cloning a staging MongoDB and MySQL to your laptop without installing a single database client</title>
      <dc:creator>Quyền</dc:creator>
      <pubDate>Wed, 07 Oct 2026 02:27:15 +0000</pubDate>
      <link>https://dev.to/phuthuycoding/cloning-a-staging-mongodb-and-mysql-to-your-laptop-without-installing-a-single-database-client-12dm</link>
      <guid>https://dev.to/phuthuycoding/cloning-a-staging-mongodb-and-mysql-to-your-laptop-without-installing-a-single-database-client-12dm</guid>
      <description>&lt;p&gt;Every week someone on my team needs a copy of our staging data on their laptop. Two&lt;br&gt;
databases, MongoDB and MySQL, a few gigabytes. For years the answer was a wiki page:&lt;br&gt;
install &lt;code&gt;mongodump&lt;/code&gt; of the right major version, install the MySQL client, run four&lt;br&gt;
commands, wait, and hope the SSH session doesn't drop halfway through a 3 GB restore.&lt;/p&gt;

&lt;p&gt;I got tired of the wiki page and wrote &lt;a href="https://github.com/phuthuycoding/dbclone" rel="noopener noreferrer"&gt;dbclone&lt;/a&gt;,&lt;br&gt;
a small Go CLI. This post is about the three decisions that made it pleasant, and the one&lt;br&gt;
limit you should know before you trust it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The trick: your Docker images already contain the tools
&lt;/h2&gt;

&lt;p&gt;If you run MongoDB or MySQL locally, you almost certainly run the official &lt;code&gt;mongo&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;mysql&lt;/code&gt; images. Those images ship &lt;code&gt;mongodump&lt;/code&gt;, &lt;code&gt;mongorestore&lt;/code&gt;, &lt;code&gt;mysqldump&lt;/code&gt; and &lt;code&gt;mysql&lt;/code&gt;.&lt;br&gt;
dbclone never installs anything on your host; every dump and restore is a &lt;code&gt;docker exec&lt;/code&gt;&lt;br&gt;
into the container you already have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌──────────────── local container ────────────────┐
source ──────────▶│ mongodump / mysqldump ──pipe──▶ mongorestore / mysql │──────────▶ target
(any profile)     └──────────────────────────────────────────────────┘   (any profile)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two consequences fell out of this that I did not plan for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version skew disappears.&lt;/strong&gt; The client that dumps is the same major version as the
server that restores, because they are the same image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any direction works.&lt;/strong&gt; The container is just the pipe. &lt;code&gt;staging → local&lt;/code&gt; is the
common case, but &lt;code&gt;local → staging&lt;/code&gt; and &lt;code&gt;staging → another server&lt;/code&gt; go through the same
code path. Your local containers are simply a built-in profile called &lt;code&gt;local&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Streams, never stages
&lt;/h2&gt;

&lt;p&gt;Each dump is piped straight into its restore. There is no dump file on disk, so there is no&lt;br&gt;
"out of disk at 90%" and no cleanup step.&lt;/p&gt;

&lt;p&gt;Big work gets its own stream. A MongoDB collection of 64 MiB or more becomes one&lt;br&gt;
&lt;code&gt;mongodump --collection | mongorestore&lt;/code&gt; pipe; everything smaller travels together in one&lt;br&gt;
extra stream. MySQL tables are split into up to &lt;code&gt;-w&lt;/code&gt; size-balanced groups, each its own&lt;br&gt;
&lt;code&gt;mysqldump --single-transaction | mysql&lt;/code&gt; pipe, and triggers, views, routines and events&lt;br&gt;
follow once the data has landed.&lt;/p&gt;

&lt;p&gt;All streams from all databases share one weighted pool of &lt;code&gt;-j&lt;/code&gt; slots (default 8) and are&lt;br&gt;
dispatched biggest first, so a slot freed by a tiny database goes straight to the large one&lt;br&gt;
instead of waiting for its siblings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloning 3 databases prod → local · pool 8 streams · max 4 streams/db

⠹ mongo:shop   orders ⇉2         [==========&amp;gt;-------------]  42%  1.2 GiB / ~2.9 GiB  18.4 MiB/s  01:07
✓ mysql:app    done              [========================] 100%  235.6 MiB           57.2 MiB/s  00:04
· mysql:crm    waiting for slot  [------------------------]   0%    0.0 b / ~80.0 MiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Flaky links and the rails that stop you from hurting yourself
&lt;/h2&gt;

&lt;p&gt;Every stream is retried three times with backoff. Because restores drop and recreate the&lt;br&gt;
object, a retry starts clean instead of appending to a half-written table. Errors that no&lt;br&gt;
retry can fix, such as access denied, fail at once.&lt;/p&gt;

&lt;p&gt;Then the part I care about most, because the tool can write to a server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing to anything but &lt;code&gt;local&lt;/code&gt; means typing the target's name, interactively or with
&lt;code&gt;-confirm staging&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-fresh&lt;/code&gt; (drop whole databases first) only works when the target is &lt;code&gt;local&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A server is never cloned onto itself.&lt;/li&gt;
&lt;li&gt;Passwords reach the tools as environment variables, never on a command line. Profiles are
stored with mode &lt;code&gt;0600&lt;/code&gt;. Every log line is scrubbed of &lt;code&gt;user:password@&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The limit you should know
&lt;/h2&gt;

&lt;p&gt;MySQL table groups each take their own &lt;code&gt;--single-transaction&lt;/code&gt; snapshot. The copy is&lt;br&gt;
therefore not one consistent point in time across tables. That is fine for development&lt;br&gt;
data, which is what this is for. It is not a backup tool, and I would not use it as one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/phuthuycoding/dbclone@latest   &lt;span class="c"&gt;# or a binary from GitHub Releases&lt;/span&gt;
dbclone &lt;span class="nt"&gt;-check&lt;/span&gt;                              &lt;span class="c"&gt;# Docker + containers OK? prints the fix if not&lt;/span&gt;
dbclone                                     &lt;span class="c"&gt;# first run: add a profile, pick FROM, TO, databases&lt;/span&gt;
dbclone &lt;span class="nt"&gt;-from&lt;/span&gt; staging &lt;span class="nt"&gt;-only&lt;/span&gt; mongo:shop,mysql:app &lt;span class="nt"&gt;-yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine interface is one package per database (&lt;code&gt;internal/driver/&amp;lt;engine&amp;gt;/&lt;/code&gt;), and&lt;br&gt;
PostgreSQL is the obvious next one. If you try it on a setup that breaks, an issue with the&lt;br&gt;
&lt;code&gt;logs/&lt;/code&gt; folder from that run is the most useful thing you can send me.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/phuthuycoding/dbclone" rel="noopener noreferrer"&gt;https://github.com/phuthuycoding/dbclone&lt;/a&gt; — MIT.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>go</category>
      <category>docker</category>
      <category>mongodb</category>
    </item>
  </channel>
</rss>
