🔥 0
0
Lesson 8 of 10 15 min +100 XP

Configuring Payment & API Keys

E-commerce apps need sensitive configuration: payment gateway keys, database credentials, third-party API tokens. Docker provides several ways to handle these securely.

The Wrong Way

Never put secrets in your Dockerfile:

# NEVER DO THIS!
FROM node:20-alpine
ENV STRIPE_SECRET_KEY=sk_live_abc123xyz
ENV DATABASE_PASSWORD=supersecret
COPY . .
CMD ["node", "server.js"]

This bakes secrets into the image. Anyone who pulls your image can extract them:

docker history your-image --no-trunc
# Shows all ENV values!

Environment Variables at Runtime

The right approach: Pass secrets when running the container.

docker run -d \
  --name payment-service \
  -e STRIPE_SECRET_KEY=sk_live_abc123xyz \
  -e STRIPE_WEBHOOK_SECRET=whsec_xyz789 \
  -e DATABASE_URL=postgresql://user:pass@db:5432/shop \
  -p 3000:3000 \
  shopflow/payment-service:1.0

Secrets exist only in the running container, not in the image.

Reading Environment Variables

Node.js:
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const dbConfig = {
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === 'production'
};

// With defaults
const port = process.env.PORT || 3000;
const logLevel = process.env.LOG_LEVEL || 'info';
Python:
import os
import stripe

stripe.api_key = os.environ['STRIPE_SECRET_KEY']

db_url = os.environ.get('DATABASE_URL', 'postgresql://localhost/shop')
debug = os.environ.get('DEBUG', 'false').lower() == 'true'

E-commerce Configuration Example

Here's a complete payment service setup:

src/config.js:
const requiredVars = [
  'STRIPE_SECRET_KEY',
  'STRIPE_WEBHOOK_SECRET',
  'DATABASE_URL',
  'REDIS_URL'
];

// Validate all required vars are set
for (const varName of requiredVars) {
  if (!process.env[varName]) {
    console.error(`Missing required environment variable: ${varName}`);
    process.exit(1);
  }
}

module.exports = {
  stripe: {
    secretKey: process.env.STRIPE_SECRET_KEY,
    webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
    currency: process.env.STRIPE_CURRENCY || 'usd'
  },
  database: {
    url: process.env.DATABASE_URL
  },
  redis: {
    url: process.env.REDIS_URL
  },
  app: {
    port: parseInt(process.env.PORT) || 3000,
    env: process.env.NODE_ENV || 'development',
    logLevel: process.env.LOG_LEVEL || 'info'
  }
};
src/server.js:
const express = require('express');
const Stripe = require('stripe');
const config = require('./config');

const app = express();
const stripe = Stripe(config.stripe.secretKey);

app.post('/api/checkout', express.json(), async (req, res) => {
  try {
    const { items, customerId } = req.body;

    const session = await stripe.checkout.sessions.create({
      customer: customerId,
      payment_method_types: ['card'],
      line_items: items.map(item => ({
        price_data: {
          currency: config.stripe.currency,
          product_data: { name: item.name },
          unit_amount: Math.round(item.price * 100)
        },
        quantity: item.quantity
      })),
      mode: 'payment',
      success_url: `${req.headers.origin}/order/success`,
      cancel_url: `${req.headers.origin}/cart`
    });

    res.json({ sessionId: session.id, url: session.url });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.post('/webhooks/stripe',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig = req.headers['stripe-signature'];

    try {
      const event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        config.stripe.webhookSecret
      );

      switch (event.type) {
        case 'checkout.session.completed':
          console.log('Payment successful:', event.data.object.id);
          // Update order status in database
          break;
        case 'payment_intent.payment_failed':
          console.log('Payment failed:', event.data.object.id);
          break;
      }

      res.json({ received: true });
    } catch (err) {
      res.status(400).send(`Webhook Error: ${err.message}`);
    }
  }
);

app.listen(config.app.port, () => {
  console.log(`Payment service running on port ${config.app.port}`);
  console.log(`Environment: ${config.app.env}`);
});

Using .env Files

For local development, create a .env file:

.env (never commit this!):
# Stripe Configuration
STRIPE_SECRET_KEY=sk_test_abc123
STRIPE_WEBHOOK_SECRET=whsec_test_xyz
STRIPE_CURRENCY=usd

# Database
DATABASE_URL=postgresql://shopflow:devpass@localhost:5432/shopflow

# Redis
REDIS_URL=redis://localhost:6379

# App Settings
NODE_ENV=development
PORT=3000
LOG_LEVEL=debug
.gitignore:
.env
.env.local
.env.*.local
Run with env file:
docker run -d \
  --name payment-service \
  --env-file .env \
  -p 3000:3000 \
  shopflow/payment-service:1.0

Environment File Per Stage

Create different env files for different environments:

.env.development    # Local development (test keys)
.env.staging        # Staging server
.env.production     # Production (real keys)
.env.development:
STRIPE_SECRET_KEY=sk_test_development123
DATABASE_URL=postgresql://dev:devpass@localhost:5432/shopflow_dev
.env.production:
STRIPE_SECRET_KEY=sk_live_production789
DATABASE_URL=postgresql://prod:${DB_PASSWORD}@prod-db.example.com:5432/shopflow
# Development
docker run --env-file .env.development ...

# Production
docker run --env-file .env.production ...

Dockerfile with ENV Defaults

Set non-sensitive defaults in Dockerfile:

FROM node:20-alpine

WORKDIR /app

# Non-sensitive defaults (can be overridden at runtime)
ENV NODE_ENV=production
ENV PORT=3000
ENV LOG_LEVEL=info

# DO NOT set sensitive values here!
# ENV STRIPE_SECRET_KEY=xxx  # WRONG!

COPY package*.json ./
RUN npm ci --only=production
COPY src/ ./src/

EXPOSE 3000
CMD ["node", "src/server.js"]

Override at runtime:

docker run -d \
  -e NODE_ENV=staging \
  -e LOG_LEVEL=debug \
  -e STRIPE_SECRET_KEY=sk_test_xxx \
  shopflow/payment-service:1.0

Docker Secrets (Swarm Mode)

For production clusters, Docker Secrets are more secure:

# Create a secret
echo "sk_live_abc123xyz" | docker secret create stripe_key -

# Use in service
docker service create \
  --name payment-service \
  --secret stripe_key \
  shopflow/payment-service:1.0

In the container, secrets appear as files:

const fs = require('fs');

// Read secret from file
const stripeKey = fs.readFileSync('/run/secrets/stripe_key', 'utf8').trim();

Inspecting Environment Variables

# See env vars of a running container
docker exec payment-service env

# Or inspect the container
docker inspect payment-service --format='{{json .Config.Env}}'

Security Checklist

  • [ ] Never put secrets in Dockerfile ENV
  • [ ] Never commit .env files to git
  • [ ] Use --env-file for local development
  • [ ] Use Docker Secrets or vault for production
  • [ ] Validate required env vars on startup
  • [ ] Use different keys for test/staging/production
  • [ ] Rotate keys periodically

Common E-commerce Environment Variables

# Payment Gateways
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
PAYPAL_CLIENT_ID=
PAYPAL_CLIENT_SECRET=

# Database
DATABASE_URL=
DATABASE_POOL_SIZE=10

# Cache
REDIS_URL=

# Email
SENDGRID_API_KEY=
FROM_EMAIL=orders@shopflow.com

# Storage (for product images)
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
S3_BUCKET=shopflow-uploads

# Third-party Services
SHIPPO_API_KEY=      # Shipping
ALGOLIA_APP_ID=      # Search
ALGOLIA_API_KEY=

# App Config
NODE_ENV=production
PORT=3000
JWT_SECRET=
CORS_ORIGIN=https://shopflow.com

Key Takeaways

  • Never bake secrets into images - use runtime environment variables
  • Use -e flag for individual variables, --env-file for many
  • Create .env files per environment (dev, staging, prod)
  • Validate required variables on app startup
  • Use Docker Secrets for production clusters
  • Never commit .env files to version control

Next, let's orchestrate our entire e-commerce stack with Docker Compose!

🧠 Quick Quiz

Test your understanding of this lesson.

1

Why shouldn't you put API keys in your Dockerfile?

2

What does the -e flag do in 'docker run -e STRIPE_KEY=xxx'?

3

How do you pass multiple environment variables from a file?

Persisting Product Data