├── .ebextensions ├── 01_setup.config ├── cdn.config ├── elasticache.config ├── options.config.dist ├── s3.config ├── securelistener.config └── sqs.config ├── .ebignore ├── .gitignore ├── Dockerrun.aws.json ├── README.md └── server_env ├── deploykeys ├── .gitkeep └── config └── proxy └── conf.d └── default.conf /.ebextensions/01_setup.config: -------------------------------------------------------------------------------- 1 | commands: 2 | # Allow to run SUDO comamnds for root. 3 | 00sudo: 4 | command: 'echo Defaults:root \!requiretty >> /etc/sudoers' 5 | 6 | container_commands: 7 | # Workaround so that Docker containers can access the parsed dynamic environment-variables 8 | # E.g. env vars like S3_ACCESS_KEYS: '`{"Ref" : "MediaAccess"}`'. By default the Dockerfile will 9 | # see this variable's value as `{"Ref" : "MediaAccess"}`, but in-fact it's value is e.g. DK32191230jfjefmqw12. 10 | # This with help of a line in the Dockerfile and Dockerrun.aws.json volumes works. 11 | # See Dockerrun.aws.json 12 | # See https://github.com/peec/laravel-docker-aws/blob/master/Dockerfile#L93 13 | 01create_mount_env: 14 | command: 'mkdir -p /tmp/envs' 15 | ignoreErrors: true 16 | 02create__env_script: 17 | ignoreErrors: true 18 | command: | 19 | touch /tmp/envs/env_file 20 | echo -n > /tmp/envs/env_file 21 | for envvar in `jq '.optionsettings | {"aws:elasticbeanstalk:application:environment"}[] | .[]' /opt/elasticbeanstalk/deploy/configuration/containerconfiguration` 22 | do 23 | temp="${envvar#\"}"; 24 | temp="${temp/=/=\"}"; 25 | echo "export ${temp};" >> /tmp/envs/env_file 26 | done 27 | -------------------------------------------------------------------------------- /.ebextensions/cdn.config: -------------------------------------------------------------------------------- 1 | Resources: 2 | MediaCDN: 3 | Type: AWS::CloudFront::Distribution 4 | Properties: 5 | DistributionConfig: 6 | PriceClass: PriceClass_100 7 | DefaultCacheBehavior: 8 | Compress: true 9 | ForwardedValues: 10 | QueryString: false 11 | # We forward header origin to allow getting font files from static and html files loaded async.. 12 | # this config is relative to server_env/proxy/conf.d/default.conf -> Access-Control-Allow-Origin 13 | Headers: ["Origin"] 14 | TargetOriginId: SiteBucketOriginId 15 | ViewerProtocolPolicy: allow-all 16 | Enabled: true 17 | Origins: [ 18 | { 19 | "DomainName": { 20 | "Fn::GetAtt": [ 21 | "AWSEBLoadBalancer", 22 | "DNSName" 23 | ] 24 | }, 25 | "Id": "SiteBucketOriginId", 26 | "CustomOriginConfig": { 27 | "HTTPPort": "80", 28 | "HTTPSPort": "443", 29 | "OriginProtocolPolicy": "http-only" 30 | 31 | } 32 | } 33 | ] 34 | Outputs: 35 | MediaCDNDistribution: 36 | Description: "Gets the media CDN Distribution Id" 37 | Value: {"Ref": "MediaCDN"} 38 | SiteCDNDomainName: 39 | Description: "Gets the site CDN" 40 | Value: { "Fn::GetAtt" : [ "MediaCDN", "DomainName" ] } -------------------------------------------------------------------------------- /.ebextensions/elasticache.config: -------------------------------------------------------------------------------- 1 | #This sample requires you to create a separate configuration file that defines the custom option settings for CacheCluster properties. 2 | 3 | Resources: 4 | MyElastiCache: 5 | Type: AWS::ElastiCache::CacheCluster 6 | Properties: 7 | CacheNodeType: 8 | Fn::GetOptionSetting: 9 | OptionName : CacheNodeType 10 | DefaultValue: cache.t2.micro 11 | NumCacheNodes: 12 | Fn::GetOptionSetting: 13 | OptionName : NumCacheNodes 14 | DefaultValue: 1 15 | Engine: 16 | Fn::GetOptionSetting: 17 | OptionName : Engine 18 | DefaultValue: redis 19 | CacheSecurityGroupNames: 20 | - Ref: MyCacheSecurityGroup 21 | MyCacheSecurityGroup: 22 | Type: AWS::ElastiCache::SecurityGroup 23 | Properties: 24 | Description: "Lock cache down to webserver access only" 25 | MyCacheSecurityGroupIngress: 26 | Type: AWS::ElastiCache::SecurityGroupIngress 27 | Properties: 28 | CacheSecurityGroupName: 29 | Ref: MyCacheSecurityGroup 30 | EC2SecurityGroupName: 31 | Ref: AWSEBSecurityGroup 32 | Outputs: 33 | ElastiCacheAddress: 34 | Description : "Address of elasticache servers" 35 | Value : {"Fn::GetAtt": [ "MyElastiCache", "RedisEndpoint.Address"]} 36 | ElastiCachePort: 37 | Description : "Port of elasticache servers" 38 | Value : {"Fn::GetAtt": [ "MyElastiCache", "RedisEndpoint.Port"]} -------------------------------------------------------------------------------- /.ebextensions/options.config.dist: -------------------------------------------------------------------------------- 1 | option_settings: 2 | "aws:elasticbeanstalk:application:environment": 3 | 4 | IS_ON_AWS: true 5 | 6 | # AWS details so docker image can retrieve resources. 7 | AWS_ACCESS_KEY_ID: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 8 | AWS_SECRET_ACCESS_KEY: "yyyyyyyyyyyyyyyyyyyyyyyyyyyy" 9 | 10 | # Github token 11 | GITHUB_OAUTH_TOKEN: "zzzzzzzzzzzzzzzzzzzzzzzzz" 12 | 13 | # Laravel specifics 14 | APP_ENV: "production" 15 | APP_DEBUG: false 16 | APP_URL: "http://localhost" 17 | APP_KEY: "base64:JIiitjB44NCn3ymIb0c+ogKLl4J4i7sLC81LfrogM7s" 18 | 19 | # DB 20 | DB_CONNECTION: "mysql" 21 | 22 | # Cache 23 | CACHE_DRIVER: 'redis' 24 | REDIS_DATABASE: 0 25 | 26 | # Session 27 | SESSION_DRIVER: 'redis' 28 | SESSION_CONNECTION: 'default' 29 | 30 | # Mail 31 | MAIL_DRIVER: 'ses' 32 | SES_KEY: '' 33 | SES_SECRET: '' 34 | 35 | # Queue 36 | QUEUE_DRIVER: 'sqs' 37 | LARAVEL_QUEUE_WORKER_TIMEOUT: 60 38 | 39 | 40 | # PHP configuration 41 | PHP_MEMORY_LIMIT: "1G" 42 | 43 | 44 | # If web folder is in a separate structure, you can deploy by git repository. 45 | APP_GIT_REPOSITORY: "" 46 | APP_GIT_BRANCH: "master" 47 | 48 | 49 | 50 | 51 | # Dynamic resources: 52 | AWS_DEFAULT_REGION: '`{"Ref": "AWS::Region"}`' 53 | SES_REGION: '`{"Ref": "AWS::Region"}`' 54 | 55 | # CDN 56 | CDN: '`{"Fn::GetAtt" : [ "MediaCDN", "DomainName" ]}`' 57 | 58 | # S3 59 | MEDIA_S3_REGION: '`{"Ref": "AWS::Region"}`' 60 | MEDIA_S3_ACCESS_KEY: '`{"Ref" : "MediaAccess"}`' 61 | MEDIA_S3_SECRET_KEY: '`{"Fn::GetAtt" : [ "MediaAccess", "SecretAccessKey"]}`' 62 | MEDIA_S3_DOMAIN_NAME: '`{"Fn::GetAtt" : [ "MediaBucket", "DomainName" ]}`' 63 | MEDIA_S3_BUCKET: '`{"Ref" : "MediaBucket" }`' 64 | MEDIA_S3_WEBSITE_URL: '`{"Fn::GetAtt": [ "MediaBucket", "WebsiteURL"]}`' 65 | MEDIA_S3_SECURE_URL: '`{"Fn::Join" : ["", [ "https://", { "Fn::GetAtt" : [ "MediaBucket", "DomainName" ] } ]]}`' 66 | 67 | # Redis 68 | REDIS_HOST: '`{"Fn::GetAtt": [ "MyElastiCache", "RedisEndpoint.Address"]}`' 69 | REDIS_PORT: '`{"Fn::GetAtt": [ "MyElastiCache", "RedisEndpoint.Port"]}`' 70 | 71 | # SQS 72 | SQS_REGION: '`{"Ref": "AWS::Region"}`' 73 | SQS_QUEUE: '`{"Ref": "LaravelSQSQueue" }`' 74 | SQS_QUEUE_NAME: '`{"Fn::GetAtt" : [ "LaravelSQSQueue", "QueueName"]}`' 75 | SQS_KEY: '`{"Ref" : "SQSAccess" }`' 76 | SQS_SECRET: '`{"Fn::GetAtt" : [ "SQSAccess", "SecretAccessKey" ]}`' 77 | 78 | 79 | "aws:elasticbeanstalk:customoption": 80 | ElasticSearchInstanceCount: 1 81 | ElasticSearchInstanceType: "t2.micro.elasticsearch" 82 | CacheNodeType: cache.t2.micro 83 | NumCacheNodes: 1 84 | Engine: redis 85 | SQSVisibilityTimeout: 90 86 | # SSL DOMAIN 87 | DomainName: '' 88 | "aws:autoscaling:asg": 89 | Cooldown: 3600 90 | "aws:autoscaling:trigger": 91 | BreachDuration: '15' 92 | EvaluationPeriods: '1' 93 | LowerThreshold: '70' 94 | UpperThreshold: '90' 95 | MeasureName: CPUUtilization 96 | Period: '15' 97 | Statistic: Average 98 | Unit: Percent 99 | "aws:elasticbeanstalk:command": 100 | DeploymentPolicy: AllAtOnce 101 | Timeout: 2000 102 | -------------------------------------------------------------------------------- /.ebextensions/s3.config: -------------------------------------------------------------------------------- 1 | Resources: 2 | MediaBucket: 3 | Type: AWS::S3::Bucket 4 | Properties: 5 | AccessControl: PublicRead 6 | CorsConfiguration: 7 | CorsRules: [ 8 | { 9 | "AllowedOrigins": ["*"], 10 | "AllowedMethods": ["GET"], 11 | "MaxAge": 3000, 12 | "AllowedHeaders": ["Authoirzation"] 13 | } 14 | ] 15 | WebsiteConfiguration: 16 | ErrorDocument: error.html 17 | IndexDocument: index.html 18 | MediaUser: 19 | Type: AWS::IAM::User 20 | MediaPolicy: 21 | Type: AWS::S3::BucketPolicy 22 | Properties: 23 | Bucket: { "Ref" : "MediaBucket" } 24 | PolicyDocument: 25 | Statement: [ 26 | { 27 | "Sid": "FullAccess", 28 | "Effect": "Allow", 29 | "Principal": { 30 | "AWS" : { "Fn::GetAtt" : [ "MediaUser", "Arn" ] } 31 | }, 32 | "Action": [ 33 | "s3:*" 34 | ], 35 | "Resource": [ 36 | { "Fn::Join" : ["", [ "arn:aws:s3:::", { "Ref" : "MediaBucket" } , "/*" ]] } 37 | ] 38 | }, 39 | { 40 | "Sid": "ReadOnlyForAnonymous", 41 | "Effect": "Allow", 42 | "Principal": { 43 | "AWS" : "*" 44 | }, 45 | "Action": [ 46 | "s3:GetObject" 47 | ], 48 | "Resource": [ 49 | { "Fn::Join" : ["", [ "arn:aws:s3:::", { "Ref" : "MediaBucket" } , "/*" ]] } 50 | ] 51 | } 52 | ] 53 | MediaAccess: 54 | Type: AWS::IAM::AccessKey 55 | Properties: 56 | UserName: { "Ref" : "MediaUser" } 57 | 58 | Outputs: 59 | MediaWebsiteURL: 60 | Description : "URL for media hosted on S3" 61 | Value : {"Fn::GetAtt": [ "MediaBucket", "WebsiteURL"]} 62 | MediaSecureURL: 63 | Description : "Name of S3 bucket to hold media content" 64 | Value : { "Fn::Join" : ["", [ "https://", { "Fn::GetAtt" : [ "MediaBucket", "DomainName" ] } ]] } 65 | MediaDomainName: 66 | Description: "Media domain name" 67 | Value: { "Fn::GetAtt" : [ "MediaBucket", "DomainName" ] } 68 | MediaAccessKey: 69 | Description: "The media s3 bucket access key" 70 | Value: { "Ref" : "MediaAccess" } 71 | MediaSecretKey: 72 | Description: "The media secret key for media bucket" 73 | Value: { "Fn::GetAtt" : [ "MediaAccess", "SecretAccessKey" ] } -------------------------------------------------------------------------------- /.ebextensions/securelistener.config: -------------------------------------------------------------------------------- 1 | option_settings: 2 | # aws:elb:listener:443: 3 | # ListenerProtocol: HTTPS 4 | # SSLCertificateId: `{"Ref" : "SiteSSL"}` 5 | # InstancePort: 80 6 | # InstanceProtocol: HTTP 7 | 8 | 9 | #SiteSSL: 10 | # Type: AWS::CertificateManager::Certificate 11 | # Properties: 12 | # DomainName: 13 | # Fn::GetOptionSetting: 14 | # OptionName : DomainName 15 | # DefaultValue: '' 16 | # DomainValidationOptions: 17 | # - DomainName: 18 | # Fn::GetOptionSetting: 19 | # OptionName : DomainName 20 | # DefaultValue: '' 21 | # ValidationDomain: 22 | # Fn::GetOptionSetting: 23 | # OptionName : DomainName 24 | # DefaultValue: '' -------------------------------------------------------------------------------- /.ebextensions/sqs.config: -------------------------------------------------------------------------------- 1 | #This sample requires you to create a separate configuration file to define the custom options for the SNS topic and SQS queue. 2 | Resources: 3 | LaravelSQSQueue: 4 | Type: AWS::SQS::Queue 5 | Properties: 6 | VisibilityTimeout: 7 | Fn::GetOptionSetting: 8 | OptionName: SQSVisibilityTimeout 9 | DefaultValue: 40 10 | SQSUser: 11 | Type: AWS::IAM::User 12 | SQSPolicy: 13 | Type: AWS::SQS::QueuePolicy 14 | Properties: 15 | Queues: [ { "Ref" : "LaravelSQSQueue" } ] 16 | PolicyDocument: 17 | Statement: [ 18 | { 19 | "Sid": "FullAccess", 20 | "Effect": "Allow", 21 | "Principal": { 22 | "AWS" : { "Fn::GetAtt" : [ "SQSUser", "Arn" ] } 23 | }, 24 | "Action": [ 25 | "sqs:*" 26 | ], 27 | "Resource":[ 28 | { "Fn::GetAtt" : [ "LaravelSQSQueue", "Arn"]} 29 | ] 30 | } 31 | ] 32 | SQSAccess: 33 | Type: AWS::IAM::AccessKey 34 | Properties: 35 | UserName: { "Ref" : "SQSUser" } 36 | 37 | Outputs : 38 | LaravelQueueURL: 39 | Description : "URL of newly created SQS Queue" 40 | Value : { Ref : "LaravelSQSQueue" } 41 | LaravelQueueARN : 42 | Description : "ARN of newly created SQS Queue" 43 | Value : { "Fn::GetAtt" : [ "LaravelSQSQueue", "Arn"]} 44 | LaravelQueueName : 45 | Description : "Name newly created SQS Queue" 46 | Value : { "Fn::GetAtt" : [ "LaravelSQSQueue", "QueueName"]} 47 | SQSAccessKey: 48 | Description: "The SQS access key" 49 | Value: { "Ref" : "SQSAccess" } 50 | SQSSecretKey: 51 | Description: "The SQS secret" 52 | Value: { "Fn::GetAtt" : [ "SQSAccess", "SecretAccessKey" ] } -------------------------------------------------------------------------------- /.ebignore: -------------------------------------------------------------------------------- 1 | # You can tell the EB CLI to ignore certain files in your project directory with a .ebignore file. 2 | # This file works like a .gitignore. When you deploy your project directory to Elastic Beanstalk 3 | # and create a new application version, the EB CLI will not include files specified by the .ebignore 4 | # in the source bundle that it creates. 5 | 6 | # If no .ebignore is present, but a .gitignore is, the EB CLI will ignore files specified in the .gitignore. 7 | # If an .ebignore file is present, the EB CLI will not read the .gitignore. 8 | 9 | 10 | 11 | # Editors 12 | /.idea 13 | 14 | 15 | # Elastic Beanstalk Files 16 | .elasticbeanstalk/* 17 | !.elasticbeanstalk/*.cfg.yml 18 | !.elasticbeanstalk/*.global.yml 19 | 20 | 21 | # Our ignores 22 | /.ebextensions/options.config.dist 23 | /web/.git 24 | 25 | 26 | # Laravel (as of 5.3). 27 | /web/node_modules 28 | /web/public/storage 29 | /web/storage/*.key 30 | /web/vendor 31 | /web/.idea 32 | /web/Homestead.json 33 | /web/Homestead.yaml 34 | /web/.env 35 | 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | /.idea 3 | 4 | 5 | # Elastic Beanstalk Files 6 | .elasticbeanstalk/* 7 | !.elasticbeanstalk/*.cfg.yml 8 | !.elasticbeanstalk/*.global.yml 9 | 10 | 11 | # Our ignores 12 | /.ebextensions/options.config 13 | /web 14 | 15 | /server_env/deploykeys/* 16 | !/server_env/deploykeys/.gitkeep 17 | !/server_env/deploykeys/config 18 | -------------------------------------------------------------------------------- /Dockerrun.aws.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSEBDockerrunVersion": 2, 3 | "volumes": [ 4 | { 5 | "name": "php-app", 6 | "host": { 7 | "sourcePath": "/var/app/current/web" 8 | } 9 | }, 10 | { 11 | "name": "nginx-proxy-conf", 12 | "host": { 13 | "sourcePath": "/var/app/current/server_env/proxy/conf.d" 14 | } 15 | }, 16 | { 17 | "name": "deploykeys", 18 | "host": { 19 | "sourcePath": "/var/app/current/server_env/deploykeys" 20 | } 21 | }, 22 | { 23 | "name": "varnish-lib", 24 | "host": { 25 | "sourcePath": "/var/lib/varnish" 26 | } 27 | }, 28 | { 29 | "name": "env-file", 30 | "host": { 31 | "sourcePath": "/tmp/envs" 32 | } 33 | } 34 | 35 | ], 36 | "containerDefinitions": [ 37 | { 38 | "name": "nginx-proxy", 39 | "image": "nginx", 40 | "essential": false, 41 | "memory": 256, 42 | "links": [ 43 | "php-app" 44 | ], 45 | "portMappings": [ 46 | { 47 | "hostPort": 80, 48 | "containerPort": 80 49 | } 50 | ], 51 | "environment": [ 52 | { 53 | "name": "NGINX_PORT", 54 | "value": "80" 55 | } 56 | ], 57 | "mountPoints": [ 58 | { 59 | "sourceVolume": "php-app", 60 | "containerPath": "/src" 61 | }, 62 | { 63 | "sourceVolume": "awseb-logs-nginx-proxy", 64 | "containerPath": "/var/log/nginx" 65 | }, 66 | { 67 | "sourceVolume": "nginx-proxy-conf", 68 | "containerPath": "/etc/nginx/conf.d", 69 | "readOnly": true 70 | } 71 | ] 72 | }, 73 | { 74 | "name": "php-app", 75 | "image": "peec/laravel-docker-aws", 76 | "essential": true, 77 | "memory": 512, 78 | "environment": [ 79 | ], 80 | "mountPoints": [ 81 | { 82 | "sourceVolume": "php-app", 83 | "containerPath": "/src" 84 | }, 85 | { 86 | "sourceVolume": "deploykeys", 87 | "containerPath": "/root/.ssh" 88 | }, 89 | { 90 | "sourceVolume": "awseb-logs-php-app", 91 | "containerPath": "/src/storage/logs" 92 | }, 93 | { 94 | "sourceVolume": "env-file", 95 | "containerPath": "/tmp/envs", 96 | "readOnly": true 97 | } 98 | ] 99 | } 100 | ] 101 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel - Scalable apps in AWS 2 | 3 | For laravel 5.3+. 4 | 5 | This can be used with [Elastic Beanstalk Multi Docker Container](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker_ecs.html). 6 | 7 | ## What is special about this setup? 8 | 9 | - Database is automatically configured. No configuration needed what so ever. (RDS) 10 | - Cache is setup on a shared redis (elasticache) server . 11 | - S3 bucket is automatically created for your app, so you can store uploaded files! 12 | - Cron that runs the scheduled laravel tasks. 13 | - Migrations are run on each deploy. 14 | - Queue tasks are automatically handeled by AWS SQS. Supervisord keeps laravel worker up to handle jobs. 15 | 16 | 17 | ## AWS Resources this setup uses out of the box 18 | 19 | - AWS Elastic Beanstalk will handle deployments. 20 | - AWS Elastic Load Balancer with Auto scaling . Meaning you can have multiple EC2 instances running your app. 21 | - AWS ElastiCache configured as the cache (redis) for laravel 22 | - AWS RDS for the database (mysql is default) 23 | - AWS S3 for storing uploaded assets (e.g. images). 24 | - AWS SQS for queues. 25 | - AWS Cloudfront created for you to use. Env variable `CDN` refers to the CDN address, just use it right away for your assets. 26 | 27 | 28 | #### Optional 29 | 30 | Optional things that can easily be configured ( Take a look at the Q / A section ). 31 | 32 | - AWS SNS to send SMS via AWS SNS. 33 | - AWS SES to send emails. 34 | 35 | 36 | 37 | 38 | # Getting Started 39 | 40 | 41 | 42 | ## Create your laravel app 43 | 44 | You have two options: 45 | 46 | - (Option 1.) Have the laravel app inside a `web` folder in the root of this repo. **recommended**. 47 | - (Option 2.) Have the laravel app at a separate git repository, and configure so that on deploy it pulls the sources for your app via git. 48 | 49 | With option 1. you also can benefit of beanstalk app rollbacks etc. Recommended. 50 | 51 | 52 | ### Option 1. have the laravel app inside this folder 53 | 54 | With option 1 you have the benefit to rollback to previous app releases. Which is a big part of beanstalk. 55 | 56 | 57 | #### Clone this repo and create a new laravel app 58 | 59 | ``` 60 | git clone https://github.com/peec/laravel-aws ~/my-deploy-root 61 | cd ~/my-deploy-root 62 | laravel new myapp 63 | 64 | # Note: change the app folder to "web". 65 | mv myapp web 66 | ``` 67 | 68 | #### The directory structure 69 | 70 | 71 | Directory structure should now be: 72 | 73 | - .ebextensions/ 74 | - web/ `This is your app location` 75 | - .gitignore 76 | - .ebignore 77 | - Dockerrun.aws.json 78 | - README.md 79 | 80 | 81 | **How to work with laravel and version control** 82 | 83 | You will work on your laravel app like you normally do inside the `web` folder. The web folder can be versioncontrolled in git like normal laravel projects is. 84 | 85 | When you push changes with `eb deploy`, elastic beanstalk will push up the web folder as-is, so don't forget to push nessecary changes and branch correctly inside the web folder before pushing `eb deploy`. 86 | 87 | ### Option 2. have the laravel app separated in its own repoistory 88 | 89 | 90 | 91 | #### Private repositories 92 | 93 | Your web-app should probably be in some private repository e.g. on github or bitbucket. The deployment process will try 94 | to clone this address, so ssh keys needs to be created and placed in `server_env/deploykeys`. 95 | 96 | ``` 97 | ssh-keygen -t rsa 98 | cp -R ~/.ssh/id_rsa* server_env/deploykeys/ 99 | ``` 100 | 101 | Add the keys to your favorite private git hosting company control panel, E.g. github profile or bitbucket profile. 102 | 103 | 104 | #### Modify APP_GIT_REPOSITORY 105 | 106 | 107 | `.ebextensions/options.config` 108 | 109 | ``` 110 | APP_GIT_REPOSITORY: "git@bitbucket.org:zzz/yyy.git" 111 | APP_GIT_BRANCH: "master" 112 | ``` 113 | 114 | #### Create a empty web folder 115 | 116 | ``` 117 | mkdir web 118 | ``` 119 | 120 | #### Deploy 121 | 122 | ``` 123 | eb deploy 124 | ``` 125 | 126 | 127 | 128 | 129 | ## Configure .ebextensions/options.config.dist 130 | 131 | ``` 132 | cp .ebextensions/options.config.dist .ebextensions/options.config 133 | ``` 134 | 135 | 136 | Configure these environment variables. 137 | 138 | | Variable | Description | 139 | | ---------------------- |:----------------------------------------------------------------------------------:| 140 | | AWS_ACCESS_KEY_ID | Get this via AWS IAM (optional) | 141 | | AWS_SECRET_ACCESS_KEY | Get this via AWS IAM (optional) | 142 | | GITHUB_OAUTH_TOKEN | Token to get composer deps. You can get this in the github profile settings. | 143 | | APP_KEY | Set it to the laravel app key found in web/.env dir. (laravel dev copy). | 144 | 145 | 146 | #### Add custom policy to the elasticbeanstalk-ec2-role 147 | 148 | 149 | Add this ( IAM -> Roles -> aws-elasticbeanstalk-ec2-role -> Inline Policies -> Create Role Policy -> Custom Policy ) and here is what you add: 150 | 151 | ``` 152 | { 153 | "Version": "2012-10-17", 154 | "Statement": [ 155 | { 156 | "Action": [ 157 | "ec2:Describe*", 158 | "cloudformation:Describe*", 159 | "elasticbeanstalk:Describe*" 160 | ], 161 | "Effect": "Allow", 162 | "Resource": "*" 163 | } 164 | ] 165 | } 166 | ``` 167 | 168 | 169 | 170 | ## Configure Laravel 171 | 172 | 173 | 174 | First we need to install some dependencies that laravel requires (also explained in laravel docs): 175 | 176 | ``` 177 | cd web 178 | composer require league/flysystem-aws-s3-v3 ~1.0 179 | composer require aws/aws-sdk-php ~3.0 180 | composer require predis/predis 181 | ``` 182 | 183 | 184 | The configuration of this deployment will automatically add a number of new environment variables available to use by laravel. 185 | Lets configure the files needed with `env()` calls where needed. 186 | 187 | 188 | 189 | 190 | `app/config/filesystems.php` 191 | 192 | ``` 193 | .... 194 | 195 | 'default' => env('IS_ON_AWS', false) ? 's3' : 'local', 196 | .... 197 | 198 | 's3' => [ 199 | 'driver' => 's3', 200 | 'key' => env('MEDIA_S3_ACCESS_KEY'), 201 | 'secret' => env('MEDIA_S3_SECRET_KEY'), 202 | 'region' => env('MEDIA_S3_REGION'), 203 | 'bucket' => env('MEDIA_S3_BUCKET'), 204 | ], 205 | .... 206 | ``` 207 | 208 | 209 | `app/config/database.php` 210 | 211 | ``` 212 | .... 213 | 214 | 215 | 'redis' => [ 216 | 217 | 'client' => 'phpredis', 218 | 219 | 'cluster' => false, 220 | 221 | 'default' => [ 222 | 'host' => env('REDIS_HOST', '127.0.0.1'), 223 | 'password' => env('REDIS_PASSWORD', null), 224 | 'port' => env('REDIS_PORT', 6379), 225 | 'database' => env('REDIS_DATABASE', 0), 226 | 'read_timeout' => 60, 227 | ], 228 | 229 | ], 230 | 231 | .... 232 | ``` 233 | 234 | `app/config/app.php` 235 | 236 | ``` 237 | .... 238 | 239 | // REMOVE THIS LINE 240 | 'Redis' => Illuminate\Support\Facades\Redis::class, 241 | 242 | .... 243 | ``` 244 | 245 | `app/config/services.php` 246 | 247 | ``` 248 | .... 249 | 250 | 'ses' => [ 251 | 'key' => env('SES_KEY'), 252 | 'secret' => env('SES_SECRET'), 253 | 'region' => env('SES_REGION', 'eu-west-1'), 254 | ], 255 | 256 | .... 257 | ``` 258 | 259 | 260 | `app/config/queue.php` 261 | 262 | ``` 263 | .... 264 | 265 | 266 | 'sqs' => [ 267 | 'driver' => 'sqs', 268 | 'key' => env('SQS_KEY'), 269 | 'secret' => env('SQS_SECRET'), 270 | 'prefix' => null, 271 | 'queue' => env('SQS_QUEUE'), 272 | 'region' => env('SQS_REGION'), 273 | ], 274 | .... 275 | ``` 276 | 277 | 278 | ## Configure AWS 279 | 280 | 281 | ### Beanstalk Multi Container Docker policy 282 | 283 | The Multi-container Docker platform requires additional ECS permissions. 284 | 285 | For more information see: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/create_deploy_docker_ecstutorial.html#create_deploy_docker_ecstutorial_role 286 | 287 | 288 | 289 | ## Install elastic beanstalk tools on your local machine 290 | 291 | http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3-install.html 292 | 293 | 294 | ## Create a beanstalk app 295 | 296 | Lets run `eb init` to start the new beanstalk app. Follow the guide below. 297 | 298 | ``` 299 | 300 | cd ~/my-deploy-root 301 | eb init 302 | 303 | Select a default region 304 | ..... 305 | 4) eu-west-1 : EU (Ireland) 306 | .... 307 | (default is 3): 4 308 | 309 | Select an application to use 310 | 1) xxxx 311 | 2) yyyy 312 | 3) [ Create new Application ] 313 | (default is 3): 3 314 | 315 | Enter Application Name 316 | (default is "myapp"): 317 | Application myapp has been created. 318 | 319 | It appears you are using Multi-container Docker. Is this correct? 320 | (y/n): y 321 | 322 | Select a platform version. 323 | 1) Multi-container Docker 1.11.2 (Generic) 324 | 2) Multi-container Docker 1.11.1 (Generic) 325 | 3) Multi-container Docker 1.9.1 (Generic) 326 | 4) Multi-container Docker 1.6.2 (Generic) 327 | (default is 1): 1 328 | Do you want to set up SSH for your instances? 329 | (y/n): y 330 | 331 | Select a keypair. 332 | 1) xxxx 333 | 2) yyyy 334 | 3) zzzz 335 | 4) [ Create new KeyPair ] 336 | (default is 4): 1 337 | 338 | ``` 339 | 340 | 341 | 342 | 343 | 344 | And to upload files, use laravel's "cloud" disk. 345 | 346 | 347 | ## Create a new enviroment 348 | 349 | Lets create a new environment for the production env. 350 | 351 | Adjust things according to needs. 352 | 353 | **Note: make sure the user executing the eb create command has privileges to create IAM users.** 354 | 355 | 356 | ``` 357 | cd ~/my-deploy-root 358 | eb create --database --database.engine mysql --database.instance db.t2.micro --database.size 10 -i t2.micro --database.username ebroot --database.password mydbpassword321 myapp-prod 359 | ``` 360 | 361 | **CHANGES** 362 | 363 | - Change database.password to your needs 364 | - Change myapp-prod to e.g. yourapp-prod or e.g. yourapp-staging, a naming convention is great here. 365 | 366 | 367 | Now wait for a while (30 minutes or so), all resources like ElastiCache, RDS, EC2 etc must be created. This can take some minutes. 368 | 369 | 370 | Open up the EB console to check status. 371 | 372 | 373 | ``` 374 | eb console 375 | ``` 376 | 377 | 378 | 379 | ## Test your app. 380 | 381 | Lets open the app in the browser. 382 | 383 | ``` 384 | eb open 385 | ``` 386 | 387 | 388 | You should see the laravel welcome page. 389 | 390 | 391 | ## Make a change in the laravel app 392 | 393 | Make some changes in e.g. the default welcome page view. 394 | 395 | And deploy the changes: 396 | 397 | ``` 398 | eb deploy 399 | ``` 400 | 401 | 402 | ## Check (or manage) your laravel app via SSH in the EC2 403 | 404 | ``` 405 | # log into the EC2 406 | eb ssh 407 | 408 | # List all the docker containers. 409 | sudo docker ps 410 | 411 | 412 | # Get the "CONTAINER ID" of the "peec/laravel-docker-aws" and run this (replace XXXXXXXXX with the CONTAINER ID ): 413 | 414 | # Lets run bash for one of the containers..... 415 | sudo docker exec -i -t XXXXXXXXX /bin/bash 416 | 417 | 418 | # List the laravel files 419 | ls -al 420 | 421 | 422 | # See the automatically generated environment variables: 423 | cat .env 424 | 425 | 426 | # all is fine, go out of the container 427 | 428 | exit 429 | 430 | # And go out of the EC2 431 | 432 | exit 433 | 434 | 435 | ``` 436 | 437 | 438 | 439 | 440 | 441 | 442 | # Q / A 443 | 444 | 445 | 446 | ### I don't want to pay for Cloudfront, how do i rem ove it ? 447 | 448 | Just remove the file `.ebextensions/cdn.config`. 449 | 450 | ``` 451 | git add --all 452 | git commit -m "remove cdn" 453 | eb deploy 454 | ``` 455 | 456 | 457 | ### I don't want to pay for elasticache, how do I remove it? 458 | 459 | Just remove the file `.ebextensions/elasticache.config`. Beanstalk will skip creating the elasticache resource. Standard cache will be used instead. 460 | 461 | ``` 462 | git add --all 463 | git commit -m "remove elasticache" 464 | eb deploy 465 | ``` 466 | 467 | ### How do i add environment variables to the laravel app? 468 | 469 | - Go to the AWS web console 470 | - Find the Elastic Beanstalk service and click into your environment. 471 | - Add laravel specific environment variables under "Configuration -> Software Configuration". 472 | 473 | 474 | ### How do I configure laravel to send e-mails via AWS SES. 475 | 476 | Go to the AWS web console and add these environment variables under "Configuration -> Software Configuration": 477 | 478 | - MAIL_DRIVER: `ses` 479 | - SES_KEY: `YOUR_AWS_ACCESS_KEY` 480 | - SES_SECRET: `YOUR_AWS_SECRET_KEY` 481 | 482 | By default the region is `region` inside the laravel/config/services.php ("ses" section) so you need to edit this in your app to your needs and redeploy. 483 | 484 | 485 | ### How do I configure laravel to send sms via AWS SNS 486 | 487 | 488 | Just install this custom notification service provider: 489 | 490 | https://github.com/peec/aws-laravel-notification 491 | 492 | Configure the keys as you want. 493 | 494 | 495 | 496 | ### What resources do i pay for when using this? 497 | 498 | - Load balancer *required* 499 | - EC2 *required* 500 | - Elasticache 501 | - Cloudfront 502 | - S3 503 | 504 | As you can see, the monthly costs can be 100$ easily. 505 | 506 | The pricing depends on how you configure .ebextensions folder. 507 | 508 | 509 | ### How do i upload files to s3? 510 | 511 | 512 | If you have followed the configuration steps explained, it's just as you would do normally: 513 | 514 | ``` 515 | Route::post('/avatars', function (){ 516 | request()->file('avatar')->store('avatars'); 517 | return back(); 518 | }); 519 | ``` 520 | 521 | 522 | ### How do i test that queuing with SQS works? 523 | 524 | Try the following: 525 | 526 | 527 | ``` 528 | php artisan make:job TestQueue 529 | ``` 530 | 531 | 532 | `app/Jobs/TestQueue.php` 533 | 534 | ``` 535 | 536 | class TestQueue implements ShouldQueue 537 | { 538 | ... 539 | 540 | 541 | public function handle() 542 | { 543 | Log::info('SQS run the job....' . date('r')); 544 | } 545 | 546 | .... 547 | 548 | 549 | } 550 | 551 | ``` 552 | 553 | 554 | Create a route 555 | 556 | ``` 557 | 558 | Route::get('/queue-test-job', function (){ 559 | 560 | $job = (new \App\Jobs\TestQueue()) 561 | ->delay(\Carbon\Carbon::now()->addMinute(1)); 562 | 563 | dispatch($job); 564 | 565 | return back(); 566 | }); 567 | ``` 568 | 569 | 570 | Deploy 571 | 572 | ``` 573 | eb deploy 574 | ``` 575 | 576 | 577 | Visit `/queue-test-job` in the production url. Wait a minute and download log files from AWS Web console. You should see 'SQS run the job...'. 578 | 579 | 580 | 581 | # Troubleshooting 582 | 583 | 584 | In the aws web console (beanstalk environment) you can download log files to troubleshoot (via Logs -> Request logs -> Last 100 lines) 585 | 586 | #### Error: You have not correctly added AWS ACCSESS / SECRET keys to Dockerrun.aws.json 587 | 588 | **The Error:** 589 | 590 | ``` 591 | An error occurred (AuthFailure) when calling the DescribeInstances operation: Authorization header or parameters are not formatted correctly. 592 | configure-instance-resources:ERROR: Could not get json from aws ec2 describe-instances 593 | ``` 594 | 595 | **Solution:** 596 | 597 | Go to Dockerrun.aws.json and configure AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY . Also the keys associated to a specific key set must have access to describe resources.. See `Custom policies` above. 598 | Redeploy `eb deploy`. 599 | 600 | -------------------------------------------------------------------------------- /server_env/deploykeys/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peec/laravel-aws/08c58c03b6ad20e82d7210e2de75b54b8385875a/server_env/deploykeys/.gitkeep -------------------------------------------------------------------------------- /server_env/deploykeys/config: -------------------------------------------------------------------------------- 1 | Host github.com 2 | StrictHostKeyChecking no 3 | 4 | Host bitbucket.org 5 | StrictHostKeyChecking no 6 | -------------------------------------------------------------------------------- /server_env/proxy/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | 2 | upstream fastcgi_backend { 3 | # php-app is the docker container hostname for the php-fpm instance. 4 | server php-app:9000; 5 | } 6 | 7 | 8 | 9 | server { 10 | listen 80 default_server; 11 | listen [::]:80 default_server ipv6only=on; 12 | 13 | 14 | # Allow uploads up to 1GB. 15 | client_max_body_size 1024M; 16 | 17 | 18 | root /src/public; 19 | index index.php index.html index.htm; 20 | 21 | location / { 22 | try_files $uri $uri/ /index.php?$query_string; 23 | } 24 | 25 | 26 | location ~ \.php$ { 27 | try_files $uri /index.php =404; 28 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 29 | fastcgi_pass fastcgi_backend; 30 | fastcgi_index index.php; 31 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 32 | include fastcgi_params; 33 | } 34 | 35 | 36 | 37 | } --------------------------------------------------------------------------------