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:

  1. Dependency waiting: Wait for TCP, HTTP, or Unix sockets before starting your app
  2. Parallel waits: Wait on multiple dependencies at once, not sequentially
  3. 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 /health endpoint.
  • Combined timeout: The -timeout applies 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

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.