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

> Learn best practices for deploying prompt-optimizer with Docker. Manage environment variables using `.env` files and Docker secrets for secure, reproducible deployments. Orchestrate with docker-compose.

- Repository: [且炼时光/prompt-optimizer](https://github.com/linshenkx/prompt-optimizer)
- Tags: best-practices
- Published: 2026-02-23

---

**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`](https://github.com/linshenkx/prompt-optimizer/blob/main/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](https://github.com/linshenkx/prompt-optimizer/blob/develop/Dockerfile)

### 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.

```dockerfile

# ---- 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:
   ```bash
   cp .env.example .env
   ```

2. **Populate sensitive values** such as API keys:
   ```text
   # .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.

```yaml
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`](https://github.com/linshenkx/prompt-optimizer/blob/main/packages/web/src/server.ts) or similar) reads configuration dynamically:

```typescript
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`](https://github.com/linshenkx/prompt-optimizer/blob/main/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](https://github.com/linshenkx/prompt-optimizer/blob/develop/docker-compose.yml)

### Health Checks and Restart Policies

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

```yaml
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`](https://github.com/linshenkx/prompt-optimizer/blob/main/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`](https://github.com/linshenkx/prompt-optimizer/blob/main/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.