The "Works on My Machine" Problem
You've been there. Your theme works perfectly on your MacBook. You push it to a colleague's Windows machine. Gulp. Node version mismatch. Ruby version incompatibility. Gem conflicts. Liquid CLI version skew.
Three hours later, you're reinstalling dependencies globally.
This friction doesn't just slow down development—it compounds team inefficiency. Each developer loses 2-3 hours weekly to environment setup, debugging, and dependency conflicts. For a team of 5, that's 40-50 hours monthly spent not shipping theme features.
Docker solves this. One Dockerfile. Every team member gets identical Node, Ruby, Liquid CLI, and system-level dependencies. No more "works on my machine" excuses.
Why Docker Matters for Shopify Theme Teams
When you're building custom themes for multiple Shopify stores, you're managing:
- Ruby version (theme testing tools)
- Node.js version (asset compilation, CSS preprocessors)
- Theme CLI version (scaffolding, uploads)
- Gems (Shopify's theme tools, testing frameworks)
- npm packages (build tools, linting, optimization)
One store uses Ruby 3.0. Another uses Ruby 2.7. One theme uses Webpack 5. Another uses Vite. Managing this across developers is a nightmare.
Docker containerizes all of this into a single reproducible image. Spin it up, get the exact same environment every time.
Dockerfile Setup for Shopify Theme Development
Here's a minimal production-grade Dockerfile for a Shopify theme:
FROM ruby:3.1-slim
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install Node.js (LTS)
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs
# Set working directory
WORKDIR /theme
# Copy Gemfile and lock
COPY Gemfile* ./
# Install Ruby gems
RUN bundle install
# Copy package.json and lock
COPY package*.json ./
# Install Node dependencies
RUN npm ci
# Expose Shopify CLI dev server port
EXPOSE 9292
# Default command
CMD ["shopify", "theme", "dev"]
This Dockerfile ensures every developer and CI/CD pipeline has: - Ruby 3.1 (consistent runtime) - Node.js 18 LTS (consistent build tools) - All gems installed via Bundler (reproducible Ruby dependencies) - All npm packages installed via npm ci (reproducible JavaScript dependencies) - Port 9292 exposed (Shopify CLI dev server)
The key insight: Docker doesn't make your theme magically better. It eliminates setup friction so developers can focus on shipping.
Docker Compose: Multi-Service Development
Most theme teams also run a local database, Redis cache, or test suite. Docker Compose orchestrates all of it:
version: '3.9'
services:
theme:
build: .
ports:
- "9292:9292"
volumes:
- .:/theme
- /theme/node_modules
environment:
- SHOPIFY_STORE_NAME=your-dev-store
- SHOPIFY_CLI_THEME_ID=123456789
depends_on:
- redis
redis:
image: redis:7-alpine
ports:
- "6379:6379"
tests:
build: .
command: npm test
volumes:
- .:/theme
depends_on:
- theme
Now developers run one command: docker-compose up. All services are running, networking is configured, and the dev environment is ready.
Real-World Setup: Multi-Store Theme Development
One client manages 7 custom themes for different Shopify stores. Each theme has different requirements:
| Store | Ruby | Node | CLI Tools | Status |
|---|---|---|---|---|
| Flagship Store | 3.1 | 18 | Webpack + Tailwind | Live |
| Secondary Store | 3.0 | 16 | Vite + PostCSS | Live |
| Test Store | 3.1 | 18 | Same as Flagship | Dev |
| Partner Store | 2.7 | 14 | Legacy build system | Deprecated |
Managing this without Docker = 4 separate local setups, conflicting global dependencies, and a 2-hour onboarding for new hires.
With Docker: One docker-compose.yml per store. Switch stores by changing the active service. Zero setup time.
Testing Themes with Docker
Testing is where Docker really shines. You can run your entire test suite in an isolated environment:
# Build stage for theme assets
FROM ruby:3.1-slim as builder
WORKDIR /theme
COPY Gemfile* ./
RUN bundle install
COPY package*.json ./
RUN npm ci
COPY . .
# Run linting
RUN npm run lint
# Run tests
RUN bundle exec rspec
# Report coverage
RUN npm run coverage
# Final stage: minimal theme image
FROM ruby:3.1-slim
WORKDIR /theme
COPY --from=builder /theme /theme
EXPOSE 9292
CMD ["shopify", "theme", "dev"]
This multi-stage build: 1. Installs dependencies 2. Runs linting (catches style issues early) 3. Runs tests (catches bugs) 4. Generates coverage reports 5. Outputs a clean production image
If any step fails, the build fails. If the build succeeds, you know the theme is production-ready.
Deployment: Docker to Shopify
Your Docker image isn't deployed to production (Shopify hosts it). But Docker ensures consistency between local testing and deployment.
A typical CI/CD flow:
1. Developer pushes to Git
↓
2. GitHub Actions / GitLab CI runs Docker build
↓
3. Tests run inside the container
↓
4. If tests pass, theme is bundled
↓
5. Bundle uploaded to Shopify via CLI
↓
6. Theme goes live
Because every developer tested in the same Docker environment, you avoid the "it passed locally but failed in production" nightmare.
Docker Best Practices for Shopify Themes
1. Use .dockerignore
Keep your image lean. Ignore unnecessary files:
node_modules
npm-debug.log
.git
.env.local
dist/
coverage/
.DS_Store
2. Pin Versions Explicitly
Don't use FROM ruby:latest. Latest changes. Pin versions:
FROM ruby:3.1.4-slim # Not ruby:3.1-slim
Same with Node, npm, and gems. Pinning ensures reproducibility.
3. Use BuildKit for Speed
Docker BuildKit caches layers more efficiently:
DOCKER_BUILDKIT=1 docker build -t my-theme .
4. Run as Non-Root User
Security best practice:
RUN useradd -m themdev
USER themdev
5. Layer Your Dockerfile Strategically
Fast-changing files (your theme code) go last. Slow-changing files (dependencies) go first:
# Early: dependencies (cache hit 95% of the time)
FROM ruby:3.1-slim
RUN apt-get install -y ...
COPY Gemfile* ./
RUN bundle install
COPY package*.json ./
RUN npm ci
# Late: your code (cache hit 0% after you change code)
COPY . .
Performance Comparison: Docker vs. Non-Docker
| Task | Without Docker | With Docker |
|---|---|---|
| Onboard new developer | 2–3 hours | 15 minutes |
| Fix dependency conflict | 45 minutes | 5 minutes (rebuild image) |
| Run full test suite | 8 minutes | 6 minutes (cached) |
| Deploy to production | 10 minutes | 2 minutes (containerized) |
| Fix "works on my machine" bug | 2 hours | 0 minutes (can't happen) |
Common Pitfalls
Pitfall 1: Mounting node_modules as a Volume
Don't do this:
volumes:
- .:/theme
- node_modules # Wrong: ties node_modules to host OS
Right way:
volumes:
- .:/theme
- /theme/node_modules # Anonymous volume, uses container's OS
Why? macOS uses a different CPU architecture (Apple Silicon) than Linux containers. npm packages with native bindings (like node-sass) will break.
Pitfall 2: Rebuilding Images for Every Code Change
You don't need to rebuild. Use volumes:
volumes:
- .:/theme # Mount your code, not a snapshot
Code changes on your machine automatically sync to the container.
Pitfall 3: Ignoring .dockerignore
Each COPY . . includes everything—git history, build artifacts, node_modules. This bloats your image. Use .dockerignore:
# .dockerignore
node_modules
.git
dist
coverage
Getting Started
- Create a Dockerfile in your theme root
- Create a
docker-compose.ymlin the same directory - Run
docker-compose up - Your theme dev server is running at
localhost:9292
Your entire team is now working in the same environment. No dependency conflicts. No version mismatches.
Ready to Eliminate "Works on My Machine"?
Docker isn't just a DevOps tool—it's a force multiplier for theme teams. It reduces onboarding time, eliminates environment friction, and makes testing reproducible.
If you're managing a theme team and environment setup is eating your time, it's worth the investment. Let's talk about containerizing your workflow.
Editorial Note
Docker feels like overkill until you're debugging why a teammate's build is broken and yours isn't. Then it becomes invaluable. For theme teams scaling beyond 2–3 people, Docker should be table stakes.
Frequently Asked Questions
Is Docker overkill for a single developer working on one theme?
Not really. Docker's main benefit is reproducibility. Even solo, you avoid environment drift over months (OS updates, global package changes). It's worth the 20-minute setup.
Will using Docker slow down my development?
Initially, builds take 2–3 minutes. But with caching, subsequent builds are 10–20 seconds. The bigger win is eliminating debugging time from environment mismatches.
Can I use Docker if I'm on Windows?
Yes, via Docker Desktop for Windows. Use WSL 2 (Windows Subsystem for Linux 2) for best performance.
What if my team uses different theme languages (Dawn vs. custom)?
Docker handles this cleanly. One Dockerfile per theme. Switch projects by changing docker-compose configurations.
How do I debug inside a Docker container?
Use docker exec -it container_name /bin/sh to open a shell. Or map your IDE's debugger to the container's port (e.g., 9229 for Node debuggers).