Deployment And Production

Packaging Your App for Deployment

In development, you mount your code as a volume for instant changes. In production, you build your code directly into the image for security, reliability, and portability. This guide shows you how to make that transition.

Development vs production images

Let's revisit what you've been doing in development and why it changes for production.

What you've been doing (development)

In the installation guide, your compose.yml looked like this:

compose.yml
services:
  php:
    image: serversideup/php:8.4-fpm-nginx
    ports:
      - 80:8080
    volumes:
      - ./:/var/www/html  # Your code mounted as a volume

This works great for development because:

  • You edit files on your computer and see changes instantly
  • No rebuild needed between changes
  • Fast iteration and debugging

What you need for production

For production, you'll create a custom image that includes your application code:

Dockerfile
FROM serversideup/php:8.4-fpm-nginx

# Copy your application code into the image
COPY --chown=www-data:www-data . /var/www/html

This is better for production because:

  • Your code can't be accidentally modified or deleted
  • The image is completely portable and self-contained
  • You can version and tag each release
  • Deployments are atomic — the new version either works or it doesn't

Creating your first production Dockerfile

Let's create a proper production Dockerfile step by step. Start by creating a file called Dockerfile in your project root.

The Dockerfile should live at the root of your project, in the same directory as your compose.yml.

Basic PHP application

For a simple PHP application, your Dockerfile might look like this:

Dockerfile
FROM serversideup/php:8.4-fpm-nginx

# Switch to root to install dependencies and copy files
USER root

# Install PHP dependencies
RUN install-php-extensions intl bcmath

# Switch back to non-root user for security
USER www-data

# Copy application files with correct ownership
COPY --chown=www-data:www-data . /var/www/html

That's it! This takes your application code and bakes it into the image.

Notice we use --chown=www-data:www-data when copying files. This ensures the web server can read your files. Learn more in our file permissions guide.

Building your image

Once you have your Dockerfile, build your image with:

Terminal
docker build -t my-app:latest .

This creates an image tagged as my-app:latest that contains your application code.

Testing your production image

Before deploying, test your production image locally:

compose.prod.yml
services:
  php:
    # Use your custom image instead of the base image
    image: my-app:latest
    ports:
      - 80:8080

Then run:

Terminal
docker compose -f compose.prod.yml up

This lets you verify your production image works correctly before deploying to real servers.

Best practices for production images

1. Use specific image tags

Don't use latest tags in production. Use specific versions:

# Bad - version can change unexpectedly
FROM serversideup/php:latest

# Good - explicitly versioned
FROM serversideup/php:8.4-fpm-nginx

2. Run as non-root user

Always run your application as a non-root user for security:

# Our images default to www-data, but explicitly switch back if you use root
USER www-data

All serversideup/php images default to running as the www-data user for security.

3. Keep secrets out of images

Never bake secrets into your images:

# ❌ Never do this
ENV APP_KEY=base64:your-secret-key

# ✅ Provide secrets at runtime via environment variables

Secrets should be provided when the container starts, not built into the image.

Image versioning strategy

When building images for deployment, tag them with meaningful versions:

Terminal
# Tag with git commit SHA
docker build -t my-app:$(git rev-parse --short HEAD) .

# Tag with semantic version
docker build -t my-app:1.2.3 .

# Tag with date and build number
docker build -t my-app:2024-10-31-build-42 .

Good versioning lets you:

  • Track which code is running in each environment
  • Roll back to previous versions quickly
  • Debug issues by knowing exactly what's deployed

What's next?

Now that you know how to package your application, you're ready to learn about SSL configuration and choosing the right hosting provider for your containerized application.

Configure SSL for your application