Skip to content

owlcorp/php-docker-templates

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

PHP Docker Templates

The problem

Do you know that feeling of:

  1. ...coming back to a project after a year and not knowing how to run it?
  2. ...reinventing the-same-but-slightly-different wheel for every small app?
  3. ...rewriting the same few paragraph of documentation for every tool you built?
  4. ...spending 2h on coding a solution for your problem, and the next 20h to prepare to share it?

Yeah, me too ;) This repository is an attempt to solve this.


How to use this repository?

  1. Clone/download this repository to a separate folder
  2. Copy files from app-skeleton into your project. Merge any changes.
  3. Search for # todo: to find any placeholders that should be changed for your individual project.
  4. Type make to see commands and enjoy your new build system :)
    You probably don't need to modify anything else, even in docker-compose files.

This repository is deliberately structured in a monorepo fashion, thus not supporting GitHub/GitLab template paradigm. In the future it will contain more examples.


Intention of this repository

This repository serves as an easy-start for bootstrapping deployment & development scaffolding for new PHP apps, or updating older ones. It contains a good base for any app, that can later be customized. The intention is to have a good opinionated toolset that can be customized by removing parts that aren't needed [yet]. The key design goal here is to have "all but the kitchen sink" but built in a way that is easily customizable.

Goals

  • Use modern, performant, yet flexible components
  • Provide base for web-only and web-cli hybrid projects
  • Provide base for cli-only apps, that isn't compromised by web-only assumptions
  • Ability to easily build for multiple architectures
  • Ensure builds can support both lightweight Alpine Linux and fully-featured Debian
  • Provide templates of documentation and unified user & developer's experience across projects

Non-goals

It is said to be opinionated, which is a keyword for "it's a base I think makes sense". It strictly stays away from providing multiple ways of doing the same thing. For example, it intentionally does not include configuration for nginx+FPM instead of Caddy, or support for projects without Git. This is to reduce complexity and make later updates easier in a project where there isn't a development team behind the scenes :)

xkcd dependency

Although the base by-design isn't infinitely flexible, it bends the rules in some places to offer a better DX. For example, it works without composer.json, or it doesn't enforce (but supports) post-build pre-deploy build command.


Conventions

The project employs certain conventions that shouldn't be changed on per-project basis:

  • Orchestration/build/chore-type tasks use Makefile
    • Many PHP developers dislike GNU make due to its complexity, but Docker native tooling is often insufficient (1, 2, 3)
    • Even Docker official orchestration hacks around limitations of their own tooling
    • In case of PHP apps, Makefiles are more like a self-documenting shell script, rather than an integral part of a build+code pipeline.
    • The alternative would be using an overcomplicated shell script, which would probably be worse than using make :)
  • App-related commands go into composer.json scripts
    • To follow standard PHP conventions, anything code-related is encapsulated in composer.json
    • The convention ensures any new developer joining the project doesn't need to learn new tooling

Build process & variables

This repository was designed to abstract complexity of multi-stage, multi-OS, and multi-architecture builds & releases, so that even small project can offer a high-quality experience for everyone. How the build & run process works depends on the audience:

  • End-users of the application
    • Regular users are encouraged to grab docker-compose.yaml and customize it as-needed. This assumption was made due to several factors:
      • Technical users who aren't developers (e.g. sysadmins) often find git workflows confusing
      • Private Git repositories are often inaccessible from production server
      • It's unrealistic to expect docker-compose.yaml to be identical across multiple environments (e.g. networks)
    • End users are intended to use docker compose up, or one of their favorite GUI-based tools like Komodo/Portainer/NAS solutions. Thus, it also involves no image build process, as users are meant to get pre-built images from a registry. This is to limit number of moving parts, reducing possibility of environment-related crashes, shifting any issues into CI/dev build phase.
    • Variables set by users in environment: are only these that application exposes, with defaults in .env and maybe for web scenarios with edge cases some from Caddyfile. This is to make docker-compose.yaml file lean. This reduces maintenance burden for developer of the application.
  • Daily development
    • Developers are intended to use docker-compose.override.yaml that extends docker-compose.yaml, ensuring the development execution environment is as close to what users run as possible.
    • The developer's stack mounts not only code, but also all configs via bind mount, while building a single dev image for current architecture and default OS specified. This allows for easy onboarding of new developers and makes it easy to come back to that project after 6 months.
    • Developers can set any .env & Caddyfile variables, but also overwrite any built-in with ENV in Dockerfile
    • The tooling provided in Makefile and automations in docker-entrypoint.sh abstract common annoyances related to permissions. Again, this ensures that developers use a similar security context as the production version, rather than running as root "because it's just for development". It also attempts to detect common user & developer's pitfalls.
  • Releasing version
    • The release process is often very complex and follows a completely different pathways than what even developers use. The complexity increases severely when multiple-OS & multiple-arch builds are desired. This is what this repository tries to solve.
    • The build process flows in layers: Makefile => docker-bake.hcl => Dockerfile => OCI images
    • The base of all build-time variables is build/build-env.sh, providing unified environment for both dev & prod images, preventing prod-only bugs
    • This repository includes some templates for documentation that can be included verbatim with the application

This looks very over-complicated!

Looking at app-skeleton you may wonder "Why are there so many tools used?!". This is exactly why this project exists. It has been extracted & abstracted from an application repository after many attempts to provide a good developer's experience, combined with providing a solid & easy to use base for users. Below is a list of decisions made along the way and the reasoning behind them, but TL;DR is none of the elements included are present to be more fancy but to achieve a goal in a robust manner.

  1. Combined web+cli Dockerfile
    • Managing multiple Dokcerfiles is often challenging, as it leads to diverging behaviors between cli/web over time, increasing maintenance burden.
    • PHP applications often start as CLI-only, but often get an API backend in the process. Including a combined Dockerfile makes this transition easier.
  2. Support for Alpine & Debian
    • In general, the community prefers Alpine Linux, due to smaller footprint increasing security while also offering perceived lightness. Thus, from the perspective of end users of the application it's beneficial to pick Alpine.
    • In the other hand, due to usage of musl instead of glibc, performance of applications running under Alpine is often much lower in comparison to Debian. In addition, a lot of bugs crop up in (not only) PHP.
    • For details check long comment in the Dockerfile.
  3. Multi-arch builds
    • Both Amazon Graviton and Apple M-series CPUs brought ARM64 to the masses. The AMD64 architecture remains popular as well. Thus, making multi-arch builds is pretty much a standard practice nowadays.
  4. build.env.sh
    • A proper build practice is to mark builds with some reference to a version control system. In all cases, this requires executing a command somewhere in the process. Docker Bake does not support that behavior out of the box.
    • This simple shell script allows for setting a uniform set of variables, with some of them being accessible to the application itself.
    • The script being used both for dev & prod environment unifies and simplifies the development experience.
  5. docker-bake.hcl
    • Docker Bake greatly simplifies multi-arch & multi-platform builds. However, the supported feature set is still quite limited.
    • While Docker Bake supports compose.yaml syntax the syntax is very limited in comparison to HCL. For example, automatic tagging would not be possible using just the YAML syntax.
  6. FrankenPHP
    • There's nothing wrong with the classic pairing of nginx+PHP-FPM. The new generation of application runtimes/servers like FrankenPHP, Swoole, or Roadrunner allow for much higher performance and often simpler configuration.
    • While many solutions require at least some changes to the application and/or the development practices to prevent memory leaks, FrankenPHP does not when running in classic mode. This allows for progressive upgrade of existing applications.
    • FrankenPHP gained an official support from The PHP Foundation signaling being the potential recommended direction for modern PHP apps.
    • Being built on top of Caddy, it is also an excellent web server for static content, with first-class HTTPS support.
  7. Makefile
    • Offering Makefile allows for very simple unified command set, like make compose-update, even if the "backend" command set changes. This allows for more flexibility across multiple projects, while keeping developer's experience unified.
    • Utilizing Makefile avoids common pitfalls, e.g. bare docker compose exec ignoring entrypoint safeguarding permissions.
    • While GNU Make can be complex, in this skeleton it serves more a role of a "standardized script" than a part of the actual build system.
  8. Folder structure for PHP.ini files instead of a single php.ini
    • Using a folder structure allows for flexibility of extending the application configuration without directly editing template-provided php.ini files.
    • Due to non-recursive directory loading behavior it greatly simplifies Dockerfile build process and limits possibilities of prod & dev environments diverging.
  9. Complex docker-entrypoint.sh
    • The entrypoint script complexity stems mainly from Docker's inflexibility of --user/user: directive. Managing permissions in such scenarios, without running containerized applications as root, is often difficult. This results in many projects resorting to simply using root for convince, or relying on end-users to manage permissions. This template aims to offer a good security-minded base, without compromising DX/UX.
    • Based on experience working withing various teams, the script attempts to prevent/warn about common mistakes.
    • The entrypoint offered in the template specifically aims to be identical in dev & prod environments and for both cli & web apps.
  10. .editorconfig
  11. Documentation (.env & README.dev.md & README.md)
    • Most developers don't like writing documentation :) This is why this repository includes some premade/skeleton of the documentation, encouraging every project to have at least very basic getting started section.

Updating template

See template-sync by Les-Tilleuls.coop.

Credits

This project has been inspired by Kévin Dunglas symfony-docker project.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published