Deploying Prompt-Optimizer Using Docker: Environment Variable Management and Production Best Practices

Build a multi-stage Docker image for prompt-optimizer, externalize all secrets via .env files or Docker secrets, and orchestrate services using the provided docker-compose.yml for secure, reproducible deployments.

Prompt-optimizer is a multi-package TypeScript project designed to optimize and manage AI prompts. When deploying prompt-optimizer using Docker, implementing proper environment variable management and following containerization best practices ensures your API keys remain secure and your deployments remain consistent across development and production environments.

Building a Production-Ready Docker Image

The repository's Dockerfile defines the container build process using Node.js 20 on Alpine Linux for a minimal footprint. Understanding each layer helps optimize the final image size and security posture.

Understanding the Dockerfile Structure

Located at Dockerfile in the repository root, the build process follows these steps:

Step Instruction Purpose
Base Image FROM node:20-alpine Uses lightweight Alpine Linux with Node.js LTS to minimize attack surface.
Working Directory WORKDIR /app Establishes a consistent path for all subsequent operations.
Dependency Caching COPY package.json pnpm-lock.yaml ./ Copies lockfiles first to leverage Docker layer caching.
Package Installation RUN corepack enable && pnpm install --frozen-lockfile Installs exact dependency versions using pnpm via Corepack.
Source Copy COPY . . Adds application source code after dependency layer is cached.
Build Step RUN pnpm build Compiles TypeScript to JavaScript using the monorepo build script.
Port Exposure EXPOSE 3000 Documents the port the web interface listens on.
Runtime Command CMD ["node", "packages/web/dist/server.js"] Starts the compiled web server as the container entry point.

Source: Dockerfile on GitHub

Optimizing with Multi-Stage Builds

To further reduce image size and eliminate build-time dependencies from the final artifact, implement a multi-stage build. This approach compiles the application in a builder stage, then copies only the necessary artifacts to a runtime stage.


# ---- Builder stage -------------------------------------------------

FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

# ---- Runtime stage -------------------------------------------------

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/packages/web/dist ./dist
COPY --from=builder /app/package.json .
RUN corepack enable && pnpm install --prod --frozen-lockfile
EXPOSE 3000
CMD ["node", "dist/server.js"]

This configuration ensures that TypeScript compilers, development dependencies, and source maps remain in the builder stage, while the final image contains only the compiled JavaScript and production dependencies.

Managing Environment Variables and Secrets

Proper configuration management separates environment-specific values from the container image, preventing sensitive data from being baked into layers that could be extracted later.

Using .env Files for Local Development

The repository includes an .env.example file that documents required variables. For local deployments:

  1. Copy the template to create a local environment file:

    cp .env.example .env
  2. Populate sensitive values such as API keys:

    # .env
    
    APP_PORT=3000
    OPENAI_API_KEY=sk-...
    REDIS_URL=redis://redis:6379
  3. Reference in Docker Compose via the env_file directive (see the Orchestration section).

Implementing Docker Secrets for Production

For production environments using Docker Swarm or Kubernetes, Docker secrets provide a more secure alternative to environment variables. Secrets are mounted as files in /run/secrets/ and never appear in the container's process environment list, reducing the risk of leakage through /proc or debugging tools.

secrets:
  openai_api_key:
    external: true

services:
  app:
    secrets:
      - openai_api_key

The application reads the secret from the environment variable when Docker injects it, requiring no code changes if already using process.env.OPENAI_API_KEY.

Avoiding Hard-Coded Configuration

Ensure the application entry point (typically packages/web/src/server.ts or similar) reads configuration dynamically:

export const CONFIG = {
  port: Number(process.env.APP_PORT ?? 3000),
  openaiKey: process.env.OPENAI_API_KEY ?? '',
  redisUrl: process.env.REDIS_URL ?? 'redis://localhost:6379',
};

This pattern allows the same image to run in development, staging, and production without rebuilding, simply by changing the injected environment.

Orchestrating with Docker Compose

The repository's docker-compose.yml defines the complete runtime environment, linking the application with reverse proxies and optional data stores.

Understanding the Compose Configuration

The standard configuration includes three primary services:

Service Image Purpose Key Configuration
app Built from Dockerfile Runs the compiled Prompt-Optimizer server. env_file: .env, ports: - "${APP_PORT}:3000", restart: unless-stopped
nginx nginx:stable-alpine Static asset serving and reverse proxy to app. volumes: - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
redis redis:7-alpine Optional caching/session store. volumes: - redis-data:/data

Source: docker-compose.yml on GitHub

Health Checks and Restart Policies

Production deployments should include health checks to ensure the container is actually serving traffic, not just running:

services:
  app:
    build: .
    env_file:
      - .env
    ports:
      - "${APP_PORT:-3000}:3000"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
    depends_on:
      - redis

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    restart: unless-stopped

  nginx:
    image: nginx:stable-alpine
    ports:
      - "80:80"
    volumes:
      - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
    restart: unless-stopped

volumes:
  redis-data:

This configuration ensures that if the application fails to respond to the health endpoint, Docker marks the container as unhealthy and can trigger restart policies or orchestrator migrations.

Production Deployment Checklist

Before promoting your container to production, verify the following criteria:

✅ Checklist Item How to Verify
Image size ≤ 200 MB Run docker images and check the repository size.
No dev dependencies in final image Execute docker run --rm -it <image> sh -c "npm ls --prod" to confirm only production packages exist.
Environment variables injected at runtime Ensure docker-compose up reads from .env without requiring image rebuilds.
Health endpoint responding curl http://localhost:3000/health must return HTTP 200.
Graceful shutdown handling docker compose stop should send SIGTERM and allow the Node process to exit cleanly without data loss.
Logs directed to stdout docker compose logs -f app should show application output; verify no log files are written inside the container.
Secrets not exposed in image layers Run docker inspect <image> and confirm OPENAI_API_KEY is not present in environment variables.
Automatic rebuild on code changes Modify source code and run docker compose up --build; verify only changed layers rebuild.

Summary

Deploying prompt-optimizer using Docker requires attention to image optimization, secret management, and orchestration configuration. Key takeaways include:

  • Use multi-stage builds to separate compilation dependencies from runtime artifacts, significantly reducing image size and attack surface.
  • Externalize all configuration via .env files for development and Docker secrets for production, ensuring sensitive API keys never exist in image layers.
  • Leverage the provided docker-compose.yml to orchestrate the application with nginx and optional Redis, implementing health checks and restart policies for resilience.
  • Verify production readiness using the deployment checklist to confirm image size, absence of dev dependencies, and proper secret handling before exposing to traffic.

Frequently Asked Questions

How do I build the prompt-optimizer Docker image for production?

Build the image using the repository's Dockerfile with a multi-stage approach to exclude development dependencies. Run docker build -t prompt-optimizer:latest . from the repository root, or use docker compose build if managing the full stack. Ensure you use the --production flag or a separate runtime stage to minimize the final image size and remove build tools.

Where should I store API keys when deploying prompt-optimizer?

Never embed API keys in the Docker image. For local development, create a .env file based on .env.example and mount it via the env_file directive in docker-compose.yml. For production deployments using Docker Swarm or Kubernetes, use Docker secrets mounted as files in /run/secrets/, which prevents keys from appearing in environment variable dumps or process listings.

Can I run prompt-optimizer without Docker Compose?

Yes, you can run the container directly using docker run commands, though Docker Compose is recommended for managing multi-service dependencies like nginx and Redis. If running standalone, expose port 3000 and mount your environment file: docker run -p 3000:3000 --env-file .env prompt-optimizer:latest. Ensure you still externalize all configuration and avoid baking secrets into the image.

How do I verify my prompt-optimizer deployment is secure?

Verify security by checking that the final image contains no development dependencies using docker run --rm -it <image> sh -c "npm ls --prod". Confirm that sensitive variables like OPENAI_API_KEY are not present in the image layers by inspecting docker inspect <image>. Additionally, ensure the health check endpoint responds correctly at /health and that logs are directed to stdout rather than files inside the container.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →