{
  "version": "https://jsonfeed.org/version/1",
  "title": "Postgres on enumerator.dev",
  "icon": "<no value>",
  "home_page_url": "https://enumerator.dev/",
  "feed_url": "https://enumerator.dev/feed.json",
  "items": [
      {
        "id": "https://enumerator.dev/how-to-migrate-data-from-one-postgres-instance-to-another/",
        "title": "How to Migrate Data From One Postgres Instance to Another",
        "content_html": "<p>My first version of willow.camp <a href=\"/should-i-use-a-managed-postgres-instance\">ran a Postgres instance on the same container as the app</a>. Before going to production, I had to migrate the data from the local Postgres instance to the managed instance.</p>\n<p>I had a whopping 50-some records in the database. So, of course,  could have used the <a href=\"https://github.com/cassiascheffer/willow_camp_cli\">willow_camp_cli</a> to migrate them, but I wanted to do it the hard way. Because sometimes the hard way is fun. At least, it&rsquo;s fun when it&rsquo;s much lower stakes than working on a large production app.</p>\n<p>Here is how I migrated the data from one database to another using an SSH tunnel, <code>pg_dump</code> and <code>pg_restore</code>.</p>\n<h2 id=\"maintenance-mode\">Maintenance Mode</h2>\n<p>I had all of two real-life users when I did this. So I didn&rsquo;t need maintenance mode, but I went ahead and used it anyway. hatchbox has a maintenance mode toggle in the UI that I turned on.</p>\n<h2 id=\"set-up-a-new-user\">Set Up a New User</h2>\n<p>Digital Ocean sets up a managed database with a root user. I wanted to create an app-specific user with access to the necessary databases.</p>\n<p>I connected to the managed database using psql and created a user, granting the necessary permissions.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-sh\" data-lang=\"sh\"><span class=\"line\"><span class=\"cl\"><span class=\"nv\">PGPASSWORD</span><span class=\"o\">=</span>CLUSTER_PASSWORD psql <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -U cluster_admin <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -h digitaloceanhost.com <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -p <span class=\"m\">25060</span> <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -d defaultdb\n</span></span></code></pre></div><div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-sql\" data-lang=\"sql\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">CREATE</span><span class=\"w\"> </span><span class=\"k\">USER</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"w\"> </span><span class=\"k\">WITH</span><span class=\"w\"> </span><span class=\"n\">PASSWORD</span><span class=\"w\"> </span><span class=\"s1\">&#39;NEW_PASSWORD&#39;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">-- Grant connect permission to the database\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">GRANT</span><span class=\"w\"> </span><span class=\"k\">CONNECT</span><span class=\"w\"> </span><span class=\"k\">ON</span><span class=\"w\"> </span><span class=\"k\">DATABASE</span><span class=\"w\"> </span><span class=\"n\">defaultdb</span><span class=\"w\"> </span><span class=\"k\">TO</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">-- Grant usage on the schema\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">GRANT</span><span class=\"w\"> </span><span class=\"k\">USAGE</span><span class=\"w\"> </span><span class=\"k\">ON</span><span class=\"w\"> </span><span class=\"k\">SCHEMA</span><span class=\"w\"> </span><span class=\"k\">public</span><span class=\"w\"> </span><span class=\"k\">TO</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">-- Grant table permissions (choose what you need)\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">GRANT</span><span class=\"w\"> </span><span class=\"k\">SELECT</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">INSERT</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">UPDATE</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">DELETE</span><span class=\"w\"> </span><span class=\"k\">ON</span><span class=\"w\"> </span><span class=\"k\">ALL</span><span class=\"w\"> </span><span class=\"n\">TABLES</span><span class=\"w\"> </span><span class=\"k\">IN</span><span class=\"w\"> </span><span class=\"k\">SCHEMA</span><span class=\"w\"> </span><span class=\"k\">public</span><span class=\"w\"> </span><span class=\"k\">TO</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">-- Grant permissions on sequences (for auto-increment/serial columns)\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">GRANT</span><span class=\"w\"> </span><span class=\"k\">USAGE</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">SELECT</span><span class=\"w\"> </span><span class=\"k\">ON</span><span class=\"w\"> </span><span class=\"k\">ALL</span><span class=\"w\"> </span><span class=\"n\">SEQUENCES</span><span class=\"w\"> </span><span class=\"k\">IN</span><span class=\"w\"> </span><span class=\"k\">SCHEMA</span><span class=\"w\"> </span><span class=\"k\">public</span><span class=\"w\"> </span><span class=\"k\">TO</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"c1\">-- Grant permissions on future tables (optional)\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">ALTER</span><span class=\"w\"> </span><span class=\"k\">DEFAULT</span><span class=\"w\"> </span><span class=\"k\">PRIVILEGES</span><span class=\"w\"> </span><span class=\"k\">IN</span><span class=\"w\"> </span><span class=\"k\">SCHEMA</span><span class=\"w\"> </span><span class=\"k\">public</span><span class=\"w\"> </span><span class=\"k\">GRANT</span><span class=\"w\"> </span><span class=\"k\">SELECT</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">INSERT</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">UPDATE</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">DELETE</span><span class=\"w\"> </span><span class=\"k\">ON</span><span class=\"w\"> </span><span class=\"n\">TABLES</span><span class=\"w\"> </span><span class=\"k\">TO</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span><span class=\"line\"><span class=\"cl\"><span class=\"k\">ALTER</span><span class=\"w\"> </span><span class=\"k\">DEFAULT</span><span class=\"w\"> </span><span class=\"k\">PRIVILEGES</span><span class=\"w\"> </span><span class=\"k\">IN</span><span class=\"w\"> </span><span class=\"k\">SCHEMA</span><span class=\"w\"> </span><span class=\"k\">public</span><span class=\"w\"> </span><span class=\"k\">GRANT</span><span class=\"w\"> </span><span class=\"k\">USAGE</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"k\">SELECT</span><span class=\"w\"> </span><span class=\"k\">ON</span><span class=\"w\"> </span><span class=\"n\">SEQUENCES</span><span class=\"w\"> </span><span class=\"k\">TO</span><span class=\"w\"> </span><span class=\"s2\">&#34;willow-camp&#34;</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span></code></pre></div><h2 id=\"export-data-from-the-old-database\">Export Data from the Old Database</h2>\n<p>To do this, I created an SSH tunnel to the hatchbox instance that hosted my app and database. Then, I used <code>pg_dump</code> to export the data. <code>-L</code> in the command below sets up port forwarding so that my local port <code>5432</code> points to the hatchbox server where Postgres is running.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\">ssh -L 5432:localhost:5432 deploy@10.0.1.100\n</span></span></code></pre></div><p>Then I created a pg_dump on my local machine through the tunnel. Note that <code>localhost:5432</code> now points through the tunnel to the hatchbox database.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\">pg_dump -Fc <span class=\"s2\">&#34;postgresql://app_user:PASSWORD@localhost:5432/myapp_production&#34;</span> &gt; myapp_primary_20250625_185627.dump\n</span></span></code></pre></div><h2 id=\"import-data-to-the-new-database\">Import Data to the New Database</h2>\n<p>I made a common mistake here. A pg_dump file includes statements like <code>ALTER TABLE public.ar_internal_metadata OWNER TO user_a1b2c3d4e5f6;</code>, which assigns ownership of a table to a user, but this user only existed in my hatchbox database.</p>\n<p>I worked around ownership issues by using the <code>--no-owner</code> and <code>--no-priviliges</code> options.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\"><span class=\"nv\">PGPASSWORD</span><span class=\"o\">=</span>CLUSTER_PASSWORD pg_restore <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -U cluster_admin <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -h digitaloceanhost.com <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -p <span class=\"m\">25060</span> <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -d defaultdb <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  --no-owner <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  --no-privileges <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  -v <span class=\"se\">\\\n</span></span></span><span class=\"line\"><span class=\"cl\">  myapp_primary_20250625_185627.dump\n</span></span></code></pre></div><h2 id=\"verify-the-data\">Verify the Data</h2>\n<p>Once pg_restore had done its job, I connected to the new database again using the <code>psql</code> command above and issued a few queries to check the data.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-sql\" data-lang=\"sql\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">SELECT</span><span class=\"w\"> </span><span class=\"n\">relname</span><span class=\"w\"> </span><span class=\"k\">as</span><span class=\"w\"> </span><span class=\"k\">table_name</span><span class=\"p\">,</span><span class=\"w\"> </span><span class=\"n\">n_live_tup</span><span class=\"w\"> </span><span class=\"k\">as</span><span class=\"w\"> </span><span class=\"n\">current_rows</span><span class=\"w\"> </span><span class=\"k\">FROM</span><span class=\"w\"> </span><span class=\"n\">pg_stat_user_tables</span><span class=\"w\"> </span><span class=\"k\">ORDER</span><span class=\"w\"> </span><span class=\"k\">BY</span><span class=\"w\"> </span><span class=\"n\">current_rows</span><span class=\"w\"> </span><span class=\"k\">DESC</span><span class=\"p\">;</span><span class=\"w\">\n</span></span></span></code></pre></div><h2 id=\"connect-the-app-to-the-new-database\">Connect the App to the New Database</h2>\n<p>Finally, I updated the database environment variables in hatchbox, which rebooted the app. I performed a quick <code>rails console</code> check to ensure the database was connected, then I took the app out of maintenance mode.</p>\n<p>Once the app was back up, I could see queries hitting the managed instance in the DigitalOcean UI, and I tore down the old database on my hatchbox server.</p>\n",
        "date_published": "2025-07-03T00:57:00+00:00",
        "url": "https://enumerator.dev/how-to-migrate-data-from-one-postgres-instance-to-another/",
        "tags": ["postgres","willow-camp"]
      },
      {
        "id": "https://enumerator.dev/should-i-use-a-managed-postgres-instance/",
        "title": "Should I use a Managed Postgres Instance?",
        "content_html": "<p>I asked myself this question while developing willow.camp and decided to use a managed instance before sharing willow.camp with the world. In this post, I’ll explain my thought process and how it changed as I finished off willow.camp’s blogging features.</p>\n<h2 id=\"hatching-a-new-app\">Hatching a New App</h2>\n<p>Once I had a rough version of willow.camp coded up, I wanted to deploy it so that I could test features like subdomains, multiple users (real people, not just me and some database seeds), and eventually custom domains.</p>\n<p>I kicked off a new app on hatchbox.io using DigitalOcean. hatchbox wires everything up to deploy a Rails app off the <code>main</code> branch of a GitHub repository. All I needed was a database, so I clicked “PostgreSQL” in hatchbox, so that my Rails app and database would run on the same server. In just a few clicks, willow.camp was up and running with 1 vCPU, 1 GB of RAM, and 25 GB of storage.</p>\n<h2 id=\"going-to-production\">Going to Production</h2>\n<p>Once I had most of the features I wanted, I had to make a decision. What do I do if my server crashes? The environment and data are ephemeral. It is easy enough to get the web app up and running again, but I’d lose all the data in my database.</p>\n<p>My requirements for the database were:</p>\n<ol>\n<li>Automated backups at a minimum in a daily interval. Despite making willow.camp for markdown, I end up saving drafts right in the UI.</li>\n<li>Access to backups for a limited period. I don’t need much more than two or three days of backups.</li>\n<li>Preferably, minimal configuration.</li>\n</ol>\n<p>I had three options:</p>\n<ol>\n<li>Take periodic snapshots of the server that I can restore in case of data loss. I didn’t see an automated way to take snapshots, so I dropped this from the list.</li>\n<li>Mount a storage volume: This would separate the storage from the container on which my app was running. This seemed like an okay option to me but I was also wary of managing a single server with the Rails app and Postgres.</li>\n<li>Use a managed Postgres instance: This met all my requirements, but it cost a bit more. I waffled on this and decided to go with it in the end.</li>\n</ol>\n<h2 id=\"why-i-chose-a-managed-postgres-instance\">Why I Chose a Managed Postgres Instance</h2>\n<p>In the past, I worked on a production app where the app and database were hosted on the same server. It constantly felt like a precarious situation. The database and the app server were competing for resources.</p>\n<p>On willow.camp I only have 1 vCPU and 1 GB of RAM. I could upsize my droplet, but then I’d lose sleep over balancing resources between the two fundamental processes that run the app.</p>\n<p>willow.camp is a tiny app and can easily run with the Rails app and Database on the same small server. I wouldn’t <em>need</em> to lose sleep over it, but I’d be thinking about it all the time.</p>\n<p>I needed peace of mind that the processes would keep running and my data would be safe. Scaling up a single instance to handle the app and database didn’t sound like a fun hobby for me. It certainly wasn’t a fun job when I worked on the production setup like this in the past.</p>\n<p>The resources on this instance are ideal for a small Rails app. With 1 vCPU and 1 GB of RAM, I can run 5 Puma threads. My response time is well under 200 milliseconds (often under 100ms), so it’ll be a while before I need to consider another CPU.</p>\n<p>Choosing a managed Postgres instance gave me peace of mind that my data was safe, my app server and my database had isolated resources, and I had the flexibility to change the app server without affecting the data. The extra $10 a month simplifies my tiny infrastructure and lets me focus on the fun parts of willow.camp.</p>\n",
        "date_published": "2025-07-01T13:04:00+00:00",
        "url": "https://enumerator.dev/should-i-use-a-managed-postgres-instance/",
        "tags": ["postgres","willow-camp"]
      }
  ]
}
