Thanks for stopping by! You might also want to check this follow-up Medium article about deploying Laravel on AWS or even download the book using the form below!
My consulting clients often ask whether it is worth porting their web application to Amazon Web Services. They are expecting better performance and reliability because AWS sounds more professional, but they’ve also heard stories of horrifying AWS bills, even though they’re not sure why.
The short answer is that by architecting your web platform the right way, you will be able to first deploy on a cheaper hosting option, while ensuring a smooth migration to AWS down the road, zero refactoring guaranteed.
The long answer is the below, an extensive checklist that will guarantee your web platform to be portable, and making the most of AWS.
1. Configuration as environment variables
The first thing I would check in an audit is whether the app configuration is retrieved from the environment variables. The environment is wherever your app could be running. It could be your developers local machines, your testing servers, your staging servers or your production servers.
An application configuration is anything that might change between environments.
- Resource handles to backing services (URIs to database, cache engine, search engine etc)
- Any credentials to external services (passwords, SSH keys, SSL certificates)
Retrieve your configuration from predefined files…
… and the config files should be initialized from environment variables.
In PHP Laravel, this is achieved by creating files into the config folder, and initialising the config files with environment variables, using the helper env().
And initialise your config files from the environment
The environment-specific configuration should not be in the source code, but rather stored in the environment.
On your test, staging and production servers, the environment is set by your deployment scripts.
On the developers local machines, and for the Laravel framework, it is stored as a .env file at the root of the project, that should never be committed to the source code repository (add it to your .gitignore file).
Configuration for your test pipelines can be stored in the .env.testing file.
Any code that tests the environment is a red flag (ifs production then … / if staging then). When local and production and staging are using different drivers (for example dev machines are storing files locally, whereas production server use a file storage service), then an abstraction layer should be used, but it should not be mixed in the code with your application business logic.
Laravel offers services facades for Storage, Cache, Queue, etc ; their role is to make abstraction of how file storage, cache, background jobs queuing, etc are achieved so the logic is independent to the environment, and so your code should be.
Doing so, the same code will use a local Redis server to store the web sessions, and a managed service ElastiCache on AWS.
There will be no last minute switch when deploying your code to production, and no last minute mistake.
2. Stateless code
A stateless app is an application program that does not record data generated in one session — such as information about user settings and events that occurred — for use in the next session with that user.
Of course your application won’t be stateless! If you build a web platform, you will usually want your users to generate as much data as possible on your service.
But its processes should be stateless, in the sense that redeploying or crashing or load balancing a user to different servers should not affect the user
This is achieved by centralising storage only in backend services, not on the application servers. By storing your sessions in a managed Redis service (like AWS ElastiCache), your user files on a file storage service (like AWS S3) and running your database on a managed AWS RDS instance, you ensure that redeploying on one application server will not interrupt your users sessions.
It also means that you can load balance your traffic on multiple application servers, and your users will always find their files. They will find them even after a deployment failed and loosing a server.
Smell-test: code using local files, local memory
A PHP application storing files on the local server it’s running on is another red flag. If you were to loose that server on the next redeployment, you’d loose user data. Such an application will not be ready for scaling and will need refactoring too.
For user sessions and API OAuth tokens, you could even handle user session data without a centralised key-value store or using the database. By using Json Web Tokens instead of random OAuth tokens, an application would rely on the client (browser) to store and send back across all this information. The data is encrypted server-side to avoid tampering, and could be stored in a cookie. In that case, the servers are storing nothing (or just the black-list for revocation), and the application processes do not have to share anything or call a backend service.
3. Allow concurrency
Now that your app is stateless, an immediate benefit is that you could run multiple versions of it, on different servers. This is known as horizontal scaling, as opposite to vertical scaling which would mean keeping running your application on one single server, and to upgrade it to a more powerful one when needed.
Not all software can be horizontally scaled (for example it is a complex problem for databases or search engines), but PHP apps can be immediately horizontally scaled just by following the 6 points in this guide.
Once your application is stateless, the next step is to separate the front-end code to the background tasks. On the first hand, web processes are processes responding immediately to your users’ browsers requests.
You might also have background jobs (like sending reports, generating invoices, crunching data, send email/push notifications). They should be executed outside of your web processes, even if they are written in PHP as well.
These jobs are to be executed by worker processes, so to not block web requests.
In the Laravel framework, use the Queue facade and a message queue server like Redis, to dispatch these blocking tasks to a separate pool of servers. When you migrate to AWS, you can use the managed message queue service SQS.
Warning: if you’re using AWS SQS to schedule critical tasks, you need to be aware of the duplicate message edge case as described by Buffer.com in this post.
4. Logs as streams, not files
Just like user generated files should not be stored locally on your app server, logs should not be stored locally either. They should be centralised as well, and streamed to a backend service, where you’ll find all logs from all your application servers in one place (even from the servers you lost or took down).
Laravel’s Log facade only stores logs in files by default, so you would need a custom configuration to stream them to PHP’s standard error output instead. If you’re running your PHP application inside Docker, you can then make use of the AWS CloudWatch driver and stream your logs to one centralised place.
Any log written into a local file on a production server.
5. Horizontal scaling, without refactoring
If you’ve followed the above points, congratulations! Your web application is ready for scaling, no refactoring required.
By separating the configuration from the code, the application becomes portable from a cheaper hosting provider to AWS.
Dispatching the blocking tasks to background workers processes allows us to offload the front-end and scale it independently to the workers processes.
By running our application as stateless processes, we can load-balance our users traffic to different servers, and store the data in backend-services.
These are the pre-requisites to horizontal scaling. It is already cost-efficient since you can scale with smaller less expensive servers.
The next step is to host your backend services in a cost-efficient way too.
6. Leverage AWS Managed Services
We’ve been only talking about the application servers (where your HTTP servers respond to web requests by executing your PHP code) so far, so what about the backend services, the database, the search engine and so on?
That’s where you can make the most of AWS. All these backend services exist as managed services on AWS, ie a pay-as-you-go billing and no server for you to maintain.
You want to use these services as much as possible, for the database (AWS RDS, DocumentDB), file storage (S3), session storage (ElastiCache), logs aggregation (CloudWatch), cache engine, search engine (AWS Managed ElasticSearch) and load-balancing.
So what’s with the cost-efficiency?
Managed services cost much less in practice than running your own servers. There are more secure and scale in a few clicks. Storing your user files on AWS S3 costs about $0.02 per GB per month after 15GB transfer per month, and is free before that.
Hosting your search engine on AWS ElasticSearch managed service is free for the smaller instance.
And so on and so forth.
More importantly they will take much less time to setup, virtually zero time for maintenance and get you to sleep better at night.