Tag: VirtioFS

  • Migrating from Docker Desktop to Colima: When Hardened Images Break

    Migrating from Docker Desktop to Colima: When Hardened Images Break

    By 10:25 AM, I’d entered what Mystery Science Theater 3000 fans call ​“Deep Hurting.” The migra­tion plan was sol­id. The back­up dis­ci­pline was com­pre­hen­sive. The exe­cu­tion? Chaos.

    I run a con­tainer­ized pro­duc­tion Mastodon instance on an 8 GB Mac mini. (Yes, I know what the cloud peo­ple say, and FYI it’s Cloudflare Tunneled for pro­tec­tion.) My Docker Desktop installation’s half-​gig RAM foot­print was eat­ing pre­cious resources. Colima promised the same Docker expe­ri­ence with­out the GUI over­head. I bud­get­ed a 1.5 hour migra­tion plan for what should’ve been a straight­for­ward run­time swap.

    Two and a half hours and sev­en crit­i­cal issues lat­er, I’d dis­cov­ered that Docker Hardened Images and Colima don’t play nice­ly togeth­er. And that dis­cov­ery mat­ters to any­one run­ning hard­ened con­tain­ers in vir­tu­al­ized environments.


    The Plan (That Didn’t Survive Contact with Reality)

    The strat­e­gy was text­book: main­te­nance win­dow approach, com­pre­hen­sive back­ups (data­base dumps, vol­ume archives, con­fig­u­ra­tion snap­shots), explic­it roll­back pro­ce­dures. I’d stop Docker Desktop, switch the Docker con­text to Colima, update one path in the Makefile I use to auto­mate tasks, and restart ser­vices. Everything uses bind mounts, so data stays on the host file sys­tem. What could go wrong?

    Everything. Everything could go wrong.

    Obsolete Makefile references

    First back­up try:

    service "db" is not running

    Wait–what’s db? I migrat­ed from ver­sion 14 to ver­sion 17 of the PostgreSQL rela­tion­al data­base sys­tem weeks ago. Switched and even switched from the default PostgreSQL image to a Docker Hardened Image (DHI), even. My com­pose files ref­er­ence db-pg17. But the Makefile’s back­up tar­gets? Still call­ing the old db ser­vice. The PostgreSQL migra­tion doc­u­men­ta­tion lived in the README file that I keep. The Makefile lived in… a dif­fer­ent men­tal con­text apparently.

    Lesson: When you migrate infra­struc­ture com­po­nents, grep for ref­er­ences every­where. Compose files, Makefiles, scripts, doc­u­men­ta­tion. ​“It’s work­ing” means ​“it’s work­ing right now,” not ​“the migra­tion completed.”

    The empty postgres17/ directory

    After resolv­ing the data­base restore issues (we’ll get there), con­tain­ers start­ed suc­cess­ful­ly. Then I ran a restart test. PostgreSQL came up empty–no data, no tables, fresh initialization.

    % ls -la postgres17/
    total 0
    drwxr-xr-x@ 2 markandsharon staff 64 Jan 7 16:31 .

    64 bytes. An emp­ty direc­to­ry. That December PostgreSQL 14 → 17 ​“migra­tion”? Created the direc­to­ry, nev­er pop­u­lat­ed it. PostgreSQL 14 data stayed in postgres14/. Docker Desktop must’ve been using cached or inter­nal storage.

    Lesson: Don’t trust that migra­tions suc­ceed­ed because ser­vices are healthy. Check the actu­al data files. Persistence isn’t per­sis­tence if noth­ing’s persisting.

    Wrong database target

    After fix­ing the Makefile, ser­vices start­ed… and instant­ly crash-looped:

    PG::UndefinedTable: ERROR:  relation "users" does not exist

    PostgreSQL was healthy. The appli­ca­tion dis­agreed. Turns out I’d restored the dump to the wrong database:

    # What I did (wrong):
    psql -U mastodon postgres < dump.sql
    # What I should have done:
    psql -U mastodon mastodon_production < dump.sql

    The mastodon_production data­base existed–it was just emp­ty. All my data went into the postgres data­base that noth­ing was read­ing. The psql command-​line client defaults to the data­base match­ing your user­name or postgres if unspec­i­fied. Explicit is bet­ter than implic­it, espe­cial­ly when you’re in a hurry.

    Version-​specific PGDATA paths

    Once data land­ed in the right data­base, I hit a new prob­lem: data did­n’t per­sist across restarts. The bind mount direc­to­ry stayed emp­ty even though PostgreSQL was run­ning and accept­ing writes.

    It turns out that my PostgreSQL DHI uses version-​specific paths:

    # My bind mount:
    - ./postgres17:/var/lib/postgresql/data
    # Actual DHI PostgreSQL data directory:
    # PGDATA=/var/lib/postgresql/17/data

    The mount shad­owed the wrong direc­to­ry. PostgreSQL wrote data to /var/lib/postgresql/17/data, which was­n’t mount­ed. Data lived in ephemer­al con­tain­er stor­age. Restart? Data gone.

    $ docker compose exec db-pg17 psql -U mastodon postgres -c "SHOW data_directory;"
           data_directory
    -----------------------------
     /var/lib/postgresql/17/data

    Lesson: Verify assump­tions. Every sin­gle one. Check SHOW data_directory; imme­di­ate­ly after con­tain­er start. Test a restart before cel­e­brat­ing success.

    I cor­rect­ed the mount path to match DHI’s expect­ed loca­tion. That’s when I found the real problem.

    The DHI + Colima Incompatibility Discovery: VirtioFS bind mount ownership failures

    After cor­rect­ing the mount path, PostgreSQL entered an imme­di­ate crash-loop:

    FATAL: data directory "/var/lib/postgresql/17/data" has wrong ownership
    HINT: The server must be started by the user that owns the data directory.

    Inside the con­tain­er, the mount­ed direc­to­ry appeared owned by the root user (user ID 0). But PostgreSQL runs as the postgres user. Permission denied.

    % docker compose run --rm --entrypoint sh db-pg17 -c "ls -ld /var/lib/postgresql/17/data"
    drwxr-xr-x 2 0 0 4096 Jan 10 16:22 /var/lib/postgresql/17/data
    # Owner: UID 0 (root), but PostgreSQL requires postgres user ownership

    Colima uses the VirtioFS sys­tem for file shar­ing. VirtioFS han­dles UID map­ping dif­fer­ent­ly than Docker Desktop’s vir­tu­al machine (VM) imple­men­ta­tion. Bind mounts that work per­fect­ly on Docker Desktop fail on Colima because the own­er­ship map­ping does­n’t translate.

    Fine. This is a known issue with Colima and some images. I’ll switch to a named volume–Docker man­ages those inter­nal­ly, so host filesys­tem per­mis­sions should­n’t matter.

    Named vol­umes still failed:

    FATAL: data directory "/var/lib/postgresql/17/data" has wrong ownership

    Wait. Named vol­umes are sup­posed to be iso­lat­ed from host file sys­tem issues. They’re man­aged entire­ly by Docker. Fresh named vol­ume, Docker cre­ates it, Docker pop­u­lates it–and it still shows wrong own­er­ship inside the DHI container.

    # Fresh named volume:
    % docker compose run --rm --entrypoint sh db-pg17 -c "ls -ld /var/lib/postgresql/17/data"
    drwxr-xr-x 2 0 0 4096 Jan 10 16:22 /var/lib/postgresql/17/data

    DHI PostgreSQL’s entry­point has envi­ron­men­tal assump­tions that Colima’s VM does­n’t sat­is­fy. The image’s secu­ri­ty hard­en­ing includes stricter own­er­ship val­i­da­tion. That val­i­da­tion does­n’t account for Colima’s vol­ume handling.

    The pragmatic trade-off

    So I had to make a decision:

    1. Debug DHI + Colima com­pat­i­bil­i­ty (unknown time invest­ment, might be unsolv­able), or
    2. Switch to the stan­dard postgres:17-alpine image (known work­ing, imme­di­ate resolution)

    Production sys­tem. Already 1.5 hours into debug­ging. Swap the image:

    # Before (DHI):
    image: dhi.io/postgres:17-alpine3.22
    volumes:
      - postgres17-data:/var/lib/postgresql/17/data
    # After (Standard):
    image: postgres:17-alpine
    volumes:
      - postgres17-data:/var/lib/postgresql/data

    PostgreSQL ini­tial­ized suc­cess­ful­ly. Data per­sist­ed across restarts. Services came up healthy.

    The trade-​off:

    • ✓ Gained: Colima com­pat­i­bil­i­ty, reli­able data per­sis­tence, onward progress
    • ❌ Lost (tem­porar­i­ly): DHI secu­ri­ty hardening–documented for future investigation

    Docker Hardened Images offer secu­ri­ty fea­tures through stricter defaults and entry­point val­i­da­tion. Those same strict require­ments reduce the com­pat­i­bil­i­ty sur­face. When you intro­duce a dif­fer­ent vir­tu­al­iza­tion envi­ron­ment (Colima’s VirtioFS instead of Docker Desktop’s VM), the hard­en­ing becomes brittleness.

    This isn’t DHI’s fault–it’s the expect­ed con­se­quence of defense-​in-​depth. But if you’re migrat­ing from Docker Desktop to Colima, test your image com­pat­i­bil­i­ty in iso­la­tion first. This is cru­cial if you are using Docker Hardened Images. Carry out these tests before migra­tion day.


    The Outcome

    Migration com­plet­ed at 11:30 AM. Zero data loss. All ser­vices healthy. Automation restored. RAM reclaimed (Docker Desktop’s over­head vs. Colima’s neg­li­gi­ble footprint).

    The real out­come was discovering–systematically, through elimination–that DHI PostgreSQL and Colima are incom­pat­i­ble with­out fur­ther inves­ti­ga­tion. I’ve doc­u­ment­ed this as a known issue. Future work: test DHI with dif­fer­ent vol­ume strate­gies, check whether new­er DHI ver­sions resolve the issue, eval­u­ate whether the secu­ri­ty delta mat­ters for a single-​user instance.

    For now, I’m run­ning stan­dard postgres:17-alpine. The migra­tion is suc­cess­ful. The secu­ri­ty regres­sion is doc­u­ment­ed and sched­uled for future inves­ti­ga­tion. Forward progress beats perfectionism.

    Key Takeaways

    Backups are your safe­ty net–use them. I restored the data­base once dur­ing this migra­tion. That restore took 30 sec­onds because I’d ver­i­fied the back­up exist­ed and was recent.

    Systematic debug­ging beats pan­ic every time. Bind mounts failed → tried named vol­umes → still failed → iso­lat­ed to image-​specific behav­ior. That pro­gres­sion ruled out host file sys­tem issues and point­ed direct­ly at image compatibility.

    Pragmatic trade-​offs beat per­fec­tion­ism. I could’ve spent hours debug­ging DHI com­pat­i­bil­i­ty. Instead, I doc­u­ment­ed the incom­pat­i­bil­i­ty, switched to stan­dard images, and moved on. The secu­ri­ty regres­sion is tracked. The pro­duc­tion sys­tem is running.

    Document fail­ures hon­est­ly; they’re learn­ing oppor­tu­ni­ties. This post exists because the migra­tion did­n’t go smooth­ly. The DHI + Colima incom­pat­i­bil­i­ty is now doc­u­ment­ed for any­one else hit­ting the same issue. That’s more valu­able than a ​“here’s how I moved from X to Y” suc­cess story.

    Migration dura­tion2.5 hours actu­al vs. 1.5 hours planned
    Issues encoun­tered7 crit­i­cal
    Data loss0 bytes
    ServicesAll healthy
    Memory reclaimed~500 MB
    Novel dis­cov­er­ies1 (DHI + Colima incompatibility)
    Trade-​offs documented1 (secu­ri­ty hard­en­ing vs. compatibility

    Running pro­duc­tion infra­struc­ture on an 8 GB Mac mini teach­es you to val­ue both resources and reli­a­bil­i­ty. Colima deliv­ers on the resources. This migra­tion deliv­ered on the reli­a­bil­i­ty… eventually.