Skip to main content

Command Palette

Search for a command to run...

Working Day-to-Day Inside my Containerized Stack

Making a Docker dev stack usable every day

Updated
5 min read
Working Day-to-Day Inside my Containerized Stack
N
Senior-level Fullstack Web Developer with 10+ years experience, including 2 years of Team Lead position. Specializing in responsive design and full-stack web development across the Vue.js and .NET ecosystems. Skilled in Azure/AWS cloud infrastructure, focused on DevOps techniques such as CI/CD. Experienced in system design, especially with software architecture patterns such as microservices, BFF (backend-for-frontend). Hands-on with Agile practices in team leading, and AI-assisted coding.

Series: Containers, Actually: Building Real Local Dev Environments
ACT III — Real Implementation: My Humhub Stack
Previous: Step-by-Step: Bootstrapping my Containerized Environment
Next: Performance, Stability, and Resource Management of my Containerized Stack

Disclaimer (NDA Notice)
This article is based on a real internal project and production-adjacent development setup.
Due to NDA constraints, some service names, commands, paths, and credentials are anonymized or simplified.

The workflows, Docker behavior, debugging techniques, and trade-offs are real.
Treat this as a practical operating manual, not a verbatim replica.

Launching containers is easy.
Working inside them daily is where most Docker setups fail.

If a stack feels fragile—if you’re afraid to rebuild, unsure where to run commands, or reliant on “restart everything” as a debugging strategy—then the system hasn’t become livable yet.

This article fixes that.


The Core Question: Where Should This Command Run?

Most day-to-day confusion boils down to one question:

Should I run this command inside a container or inside WSL?

There is no universal answer—but there is a consistent rule.

Rule of Thumb

  • Commands that depend on the app runtime → run inside the container

  • Commands that manage the environment or repo → run inside WSL

Let’s make that concrete.

Commands That Belong in Containers

These commands assume:

  • A specific runtime version

  • App-level dependencies

  • Access to internal service networking

Examples:

docker-compose exec app php artisan migrate
docker-compose exec app php artisan queue:work
docker-compose exec app composer install

Why inside the container?

  • PHP version is guaranteed

  • Extensions are installed

  • Network hostnames (mysql, redis) resolve correctly

Running these in WSL would quietly reintroduce drift.

Commands That Belong in WSL

These commands manage your workspace:

git pull
git checkout feature-x
docker-compose up -d
docker-compose ps

WSL is your control plane. Containers are execution environments.

Blurring this boundary leads to confusion.


Logs and Debugging: Stop Guessing

Viewing Logs the Right Way

To inspect logs for a service:

docker-compose logs app

To follow logs live:

docker-compose logs -f web

This is always better than:

  • Guessing

  • Restarting blindly

  • Adding random echo statements

Debugging Order (Important)

When something breaks, debug in this order:

  1. Container status

     docker-compose ps
    
  2. Service logs

     docker-compose logs app
    
  3. Connectivity

     docker-compose exec app ping mysql
    
  4. App-level errors

Most issues are visible in logs within 30 seconds—if you look.


Rebuilding Images Safely (Without Nuking Everything)

Rebuilding is inevitable. Fear of rebuilding means the system is brittle.

Safe Rebuild (Most Common)

docker-compose build app
docker-compose up -d app

This:

  • Rebuilds only the app image

  • Restarts only that service

  • Preserves volumes and data

Full Rebuild (When Dependencies Change)

docker-compose build
docker-compose up -d

Use when:

  • Dockerfile changes

  • System packages change

  • PHP extensions change

Nuclear Rebuild (Last Resort)

docker-compose down -v
docker-compose build --no-cache
docker-compose up -d

This:

  • Deletes containers

  • Deletes volumes

  • Rebuilds everything from scratch

Only do this when you intend to reset state.


Database Access: Treat It Like Production

Accessing the Database via Container

docker-compose exec mysql mysql -u app -p

This ensures:

  • Correct version

  • Correct credentials

  • Correct data

Avoid connecting to MySQL installed on Windows. That defeats the entire architecture.

Inspecting Data Without Fear

Use:

  • CLI tools inside the container

  • Admin UIs exposed via ports (when available)

Never edit database files directly.
Volumes exist precisely so you don’t have to.


Resetting State (Intentionally, Not Accidentally)

Sometimes you need a clean slate.

Reset App State Only

docker-compose exec app php artisan migrate:fresh

This:

  • Drops and recreates schema

  • Keeps containers intact

Reset Database Completely

docker-compose down
docker volume rm yourproject_mysql-data
docker-compose up -d

Explicitly deleting the volume is a feature, not a failure.

Resetting state should feel deliberate—not risky.


Hot Reloads and File Watching

Why Bind Mounts Matter

Your app code is bind-mounted:

volumes:
  - ./app:/var/www/html

That means:

  • Code changes reflect immediately

  • No rebuild required

  • Containers stay running

File Watching Caveats (Windows + WSL)

File watchers rely on filesystem events.

To keep them reliable:

  • Keep code inside WSL filesystem

  • Avoid /mnt/c

  • Limit antivirus scanning Docker directories

If hot reload feels flaky, it’s almost always a filesystem issue—not Docker.


When Things Feel “Off”

Here’s a diagnostic checklist:

  • Is code inside WSL?

  • Are containers restarting?

  • Are logs clean?

  • Did you rebuild after changing dependencies?

  • Are you running commands in the right place?

If you can answer those confidently, debugging becomes boring—in the best way.


Living With the Stack Daily

A livable stack has these qualities:

  • You’re not afraid to rebuild

  • Logs are your first instinct

  • State resets are intentional

  • Commands have clear homes

  • Performance is predictable

When those are true, Docker stops being “in the way” and becomes invisible infrastructure.

That’s the goal.


What Comes Next

With daily workflows established, the final pieces are:

  • Performance tuning

  • Resource optimization

  • Long-term maintenance habits

  • Lessons learned over time

That’s where we go next—because the real test of any dev environment isn’t day one.

It’s day fifty.

A good stack doesn’t just start.
It stays usable.

Containers, Actually: Building Real Local Dev Environments

Part 8 of 18

This series explores full-stack local development with Docker—from core concepts and best practices to a real Windows implementation. The goal is to understand how things run, why they work, and how to build reproducible production environment.

Up next

Step-by-Step: Bootstrapping my Containerized Environment

From zero to a running multi-service stack