You have Docker Compose working. Your containers start. But every few deploys, something fails because the app started before the database was ready. You added wait-for-it to solve this—and it worked. But now you need more: waiting on multiple services, TCP port checks, or generating configuration files from templates at startup.
That’s where dockerize comes in. It’s a single binary that handles dependency waiting, TCP health checks, and template-based config generation—all in one tool. If wait-for-it is a gatekeeper, dockerize is a full operations assistant that prepares your container environment before your app even sees it.
In this tutorial, we’ll show you how to replace basic startup scripts with dockerize, wait on multiple dependencies simultaneously, and generate configuration files from templates at container startup.
The Problem: When wait-for-it Isn’t Enough
wait-for-it solved the immediate problem: making containers wait for a single TCP port. But real applications often need more:
- Multiple dependencies: Your app needs both PostgreSQL and Redis before it can start.
- Non-TCP services: Some services don’t expose a TCP port you can check.
- Configuration generation: You need to inject container IP addresses or environment variables into config files at runtime.
- Timeout management: You want different timeout values for different services.
You could chain multiple wait-for-it calls and write shell scripts for config generation. Or you could use dockerize, which handles all of this in a single tool.
What Is dockerize?
dockerize is a Go binary that provides three core features:
- Dependency waiting: Wait for TCP, HTTP, or Unix sockets before starting your app
- Parallel waits: Wait on multiple dependencies at once, not sequentially
- Template generation: Generate config files from templates using environment variables
It’s commonly used in Docker entrypoints to ensure services are ready and configured before the main application starts. Unlike wait-for-it’s pure-bash approach, dockerize is a compiled binary—but it’s statically linked, small (~10MB), and has zero runtime dependencies.
Prerequisites
To follow this guide, you’ll need:
- A server running Docker and Docker Compose
- Basic familiarity with Docker Compose YAML syntax
- A multi-service application (we’ll use a Node.js app with PostgreSQL and Redis)
Hosting recommendation: For production workloads with multiple services, we recommend a Cloud VPS with at least 2 vCPUs and 4GB RAM. This gives you enough resources for your app, database, and cache without contention.
Quick Start: Single Dependency
Let’s start with the simplest case: waiting for a single database before starting an app.
Step 1: Add dockerize to Your Container
Add dockerize to your Dockerfile. The official recommendation is to download it during build:
FROM node:20-alpine
# Install dockerize
ENV DOCKERIZE_VERSION v0.8.0
RUN apk add --no-cache openssl \
&& wget -O dockerize.tar.gz https://github.com/powerman/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz \
&& tar -C /usr/local/bin -xzvf dockerize.tar.gz \
&& rm dockerize.tar.gz
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Use dockerize in the entrypoint
CMD ["dockerize", "-wait", "tcp://db:5432", "-timeout", "60s", "node", "server.js"]
Step 2: Update docker-compose.yml
Your Compose file doesn’t need special changes—dockerize handles the waiting logic:
services:
app:
build: .
depends_on:
- db
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=myapp
Step 3: Test It
Run docker compose up and watch the logs. You’ll see the app container start, wait for the database to accept connections, then launch your Node.js application. No more crash loops.
Waiting for Multiple Dependencies
Real applications often need multiple services. A typical web app might need PostgreSQL for data, Redis for sessions, and an external API. With dockerize, you specify multiple -wait flags:
CMD ["dockerize", \
"-wait", "tcp://db:5432", \
"-wait", "tcp://redis:6379", \
"-wait", "http://api:8080/health", \
"-timeout", "120s", \
"node", "server.js"]
Key points:
- Parallel waiting: All waits happen simultaneously, not sequentially. If PostgreSQL takes 10 seconds and Redis takes 5, total wait time is 10 seconds—not 15.
- HTTP health checks: The
http://scheme checks HTTP endpoints, not just TCP ports. Useful for APIs that have a/healthendpoint. - Combined timeout: The
-timeoutapplies to the entire wait phase, not per-service.
Template Generation: Dynamic Config at Startup
This is where dockerize really shines. Instead of baking config files into your Docker image, you generate them at container startup using templates and environment variables.
The Problem with Static Configs
Say your app needs an nginx.conf with the upstream server’s IP address. You can’t know this at build time—the IP is assigned at runtime. Traditional solutions:
- Shell scripts: Write sed/awk commands to modify configs (fragile, hard to read)
- Entrypoint wrappers: Custom scripts that generate configs before launching the app (maintenance burden)
- Environment variable support: Hope your app reads config from env vars (not always possible)
The dockerize Solution
With dockerize, you write a template file using Go template syntax, and dockerize generates the final config at startup:
Create nginx.conf.tmpl:
upstream app {
server {{ .Env.APP_HOST }}:{{ .Env.APP_PORT }};
}
server {
listen 80;
location / {
proxy_pass http://app;
}
}
In your Dockerfile:
CMD ["dockerize", \
"-template", "/etc/nginx/nginx.conf.tmpl:/etc/nginx/nginx.conf", \
"-wait", "tcp://app:3000", \
"nginx", "-g", "daemon off;"]
At startup, dockerize reads nginx.conf.tmpl, replaces {{ .Env.APP_HOST }} and {{ .Env.APP_PORT }} with the actual environment variables, writes the result to /etc/nginx/nginx.conf, then starts nginx.
Comparison: dockerize vs wait-for-it
Both tools solve the same core problem, but they differ in scope and features:
| Feature | wait-for-it | dockerize |
|---|---|---|
| Language | Pure bash | Go binary |
| Dependencies | None (bash only) | None (static binary) |
| Multiple waits | Sequential only | Parallel |
| HTTP checks | No | Yes |
| Template generation | No | Yes |
| Binary size | ~5KB (script) | ~10MB |
| Best for | Simple single-service waits | Complex multi-service setups |
When to Use Which Tool
| Scenario | Recommendation |
|---|---|
| Single database dependency | wait-for-it — simpler, smaller, no need for extra features |
| Multiple services (db + cache + api) | dockerize — parallel waits reduce startup time |
| Need HTTP health check | dockerize — only option that supports HTTP |
| Runtime config generation | dockerize — template feature handles this |
| Minimal image size critical | wait-for-it — 5KB vs 10MB |
| Alpine/distroless containers | dockerize — static binary works everywhere |
Common Issues and Fixes
Issue: “dial tcp: lookup db: no such host”
Cause: The database container isn’t running or Docker networking hasn’t resolved the hostname yet.
Fix: Add depends_on in docker-compose.yml. While dockerize waits for the TCP port, it can’t wait for DNS resolution if the container doesn’t exist.
Issue: Timeout expires before database is ready
Cause: Your database takes longer to initialize than the configured timeout (common with PostgreSQL initial setup).
Fix: Increase the timeout. For first-time database initialization, use -timeout 180s or higher.
Issue: Template variables not being replaced
Cause: Environment variables aren’t set in the container, or template syntax is incorrect.
Fix: Verify env vars with docker compose exec app env. Check template syntax—use {{ .Env.VAR_NAME }} (note the capital E in Env).
Putting It All Together: Production Example
Here’s a complete docker-compose.yml with dockerize handling startup orchestration and config generation:
services:
app:
build: .
depends_on:
- db
- redis
environment:
- DATABASE_URL=postgres://appuser:secret@db:5432/production
- REDIS_URL=redis://redis:6379
- APP_HOST=app
- APP_PORT=3000
command: >
dockerize
-wait tcp://db:5432
-wait tcp://redis:6379
-timeout 120s
-template /app/config.tmpl:/app/config.json
node server.js
nginx:
image: nginx:alpine
depends_on:
- app
volumes:
- ./nginx.conf.tmpl:/etc/nginx/nginx.conf.tmpl:ro
command: >
dockerize
-wait http://app:3000/health
-timeout 60s
-template /etc/nginx/nginx.conf.tmpl:/etc/nginx/nginx.conf
nginx -g "daemon off;"
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=production
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
Summary
dockerize is the upgrade path when wait-for-it isn’t enough. Use it when you need:
- Multiple dependency waiting in parallel
- HTTP health checks (not just TCP)
- Runtime configuration file generation from templates
For simple single-database setups, wait-for-it remains a perfectly good choice. But as your Docker Compose stacks grow in complexity, dockerize scales with you—handling the orchestration logic so your application code doesn’t have to.
Related Reading
- Control Docker Compose Startup Order with wait-for-it — the simpler alternative for single dependencies
- Why Your Docker Container Keeps Restarting (and How to Fix It) — troubleshooting crash loops
- WAF vs Firewall vs Managed Security: What Small Teams Need — secure your Docker host
Need a Canadian-hosted VPS for your Docker workloads? Our Cloud VPS plans include 100% Canadian data residency, SOC 2 certified data centres in Vancouver and Toronto, and 24/7 support from engineers who understand container orchestration. Contact us to discuss your deployment requirements.
Be First to Comment