# Deploying to production

Deploying an application to production can be a daunting task. In this section, we will cover a few different methods of deploying the containers to a production environment, from using docker-compose with a single host, to deploying the changes on an AWS Fargate cluster that can scale on demand.

I recommend reading Best Practices section before deploying to production, as we cover in-depth topics such as handling application state, databases and observability.

# Using docker-compose

WARNING

This solution only works when you only have a single server, and don't require to scale beyond that.

A full working Laravel 7 example can be found at https://github.com/danursu/laradocker-laravel-7.

If you've followed the local configuration guide, you should already have a working production docker-compose.yml file. The file should be self-explanatory, we are not using any volumes, and we are injecting environment variables from a local file. This way our secrets are not committed to source control.

Notice

The deploy section of the configuration does not apply when running using docker-compose.

Build and run the app, by executing the command on the production server:

docker-compose -f docker-compose.yml up -d --build

Your application is accessible on port 80 on your server.

# Reverse proxy + TLS

We recommend that you use a reverse proxy and enable HTTPS with Let's encrypt certificates, as described in advanced configuration.

You will need to add your website hostname as an environment variable on the nginx container, and remove port 80 from the exposed ports, as the traffic will be forwarded through the reverse proxy container.

nginx:
  environment:
    VIRTUAL_HOST: example.domain.com

Replace example.domain.com with your actual domain name. This is the domain that Let's encrypt will generate a certificate for.

Your application is accessible on port 443 on your server.

# Using docker swarm

Docker swarm requires you to use a registry, you can either use a public one like hub.docker.com or a private one like AWS ECR or Google Cloud GCR. You can also host your own registry, but this is outside of the scope of this article.

  1. First step is to enable swarm mode on your docker engine, if you haven't done so already.
docker swarm init
  1. The base docker-compose.yml file is configured to work with docker swarm out of the box.
# docker-compose.yml
version: "3.8"
services:
    nginx:
        build:
            context: .
            dockerfile: ./docker/nginx.Dockerfile
        image: 999999999999.dkr.ecr.us-east-1.amazonaws.com/test:nginx
        restart: always
        depends_on:
            - php
        ports:
            - "80"
        networks:
            - default
        deploy:
            replicas: 1
            resources:
                limits:
                    cpus: '0.50'
                    memory: 50M
                reservations:
                    cpus: '0.25'
                    memory: 30M
            restart_policy:
                condition: any
                delay: 5s
                max_attempts: 5
                window: 120s

    php:
        build:
            context: .
            dockerfile: ./docker/php.Dockerfile
        image: 999999999999.dkr.ecr.us-east-1.amazonaws.com/test:php
        working_dir: /app
        env_file: .env
        restart: always
        expose:
            - "9000"
        deploy:
            replicas: 1
            resources:
                limits:
                    cpus: '0.50'
                    memory: 128M
                reservations:
                    cpus: '0.25'
                    memory: 64M
            restart_policy:
                condition: any
                window: 120s

Note how we specify the container image, which is the path to the registry. In this particular example, we point to AWS ECR. Replace 999999999999 with your AWS account id, and us-east-1 with the region where your ECR registry is located.

Docker swarm allows us to specify more options about how each service should behave once deployed, and limit memory and CPU for each container. More about the options here https://docs.docker.com/compose/compose-file/#deploy

  1. We will use docker-compose command to push to our registry.
# Build the docker images
docker-compose -f docker-compose.yml build

# Push the images to the registry
docker-compose -f docker-compose.yml push
  1. Now SSH into your production server and copy the docker-compose.yml files
ssh user@your-server.example.com
mkdir -p /sites/laravel-app

// Copy the files accross
scp docker-compose.yml user@your-server.example.com:/sites/demo-app/
  1. Deploy the stack to docker swarm. (on your production server)
cd /sites/laravel-app/
docker stack deploy --compose-file docker-compose.yml -f docker-compose.prod.yml laravel-app

Note: laravel-app is just a namespace in the swarm. You can name it anything you want, but each app requires one.

  1. Check that it's running
docker stack services laravel-app

Try connecting to your app http://your-server.example.com

# Removing the stack

To remove our test app type

docker stack rm laravel-app

# ECS Fargate

WARNING

At the moment of writing, the ECS integration with docker-compose is in beta. You will need to enable Experimental features from the docker preferences.

Before you begin, you will need to have AWS CLI configured with credentials that allow access to your AWS account. Follow the official AWS documentation to get the CLI configured.

TIP

The IAM user that is used to create the cluster must have AmazonECS_FullAccess policy. You can start with a user that has full access to test with, and then follow the least privilege access to restrict access to only necessary resources.

  1. Configure docker-compose to work with ECS, by creating a new context.
docker context create ecs aws

# You can name the context aws, or anything else you prefer
? Select AWS Profile new profile
? profile name default
? Region us-east-1
? Enter credentials
Successfully created ecs context "aws"

# Alternatively you can create the profile in non-interactive mode
docker context create ecs --profile default --region us-east-1 aws

If you receive the error this tool requires the "new ARN resource ID format". then login to your AWS account and go to https://console.aws.amazon.com/ecs/home?region=us-east-1#/settings. Under Amazon ECS ARN and resource ID settings set all resources to Enabled.

  1. Create a new ECR repository. You can skip this step if you already have an ECR repository, or if you're planning to use a docker repository.
aws ecr create-repository --repository-name laravel
  1. Update the docker-compose.yml file to include your repository image. Find the image entry for nginx and php in the docker-compose.yml file and update it to point to the ECR repository you created in the previous step.
services:
  nginx:
    image: 999999999999.dkr.ecr.us-east-1.amazonaws.com/laravel:nginx

  php:
    image: 999999999999.dkr.ecr.us-east-1.amazonaws.com/laravel:php

Replace 999999999999 with your AWS account id, and us-east-1 with the region where your ECS service will be deployed.

  1. Build the application and start it up locally to ensure it works as expected
# Make sure you are using the local context
docker context use default

# Build and start up the application
docker-compose -f docker-compose.yml up --build
  1. Push the application images to docker registry or ECR. ECS will pull the images from the registry when deploying your application.

Optional, if you are using ECR you will need to authenticate from your terminal

aws ecr get-login-password --region us-east-1 | docker login -u AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com

Replace us-east-1 with the region where your registry is located, and <AWS_ACCOUNT_ID> with your AWS account ID.

docker-compose -f docker-compose.yml push
  1. Deploy the application to ECS. Behind the scenes, docker will generate a cloudformation template that will be used to deploy the infrastructure and application.
# Switch context to aws
docker context use aws

# Deploy
docker compose up
  1. Check the application is running and logs
# This command will list running applications on ECS
docker compose ps

# This command will display the application logs
docker compose logs
  1. Check the application in the browser, run docker compose ps to fetch the public DNS that your application is running at, it will look something like: dockercomposelaravel8LoadBalance-2343241123.us-east-1.elb.amazonaws.com.

Head over to http://dockercomposelaravel8LoadBalance-2343241123.us-east-1.elb.amazonaws.com

# Cleanup

To remove the ECS application and related infrastructure, run:

docker compose down

TIP

If docker compose down command fails, you can login to your AWS account console, and navigate to Cloud Formation. From there you can manually delete the stack.