Flyimg turns a URL into a resized and cached image
Dockerized application that resizes and crops images on the fly, delivering optimized images in formats such as AVIF, WebP, MozJPEG, or PNG using ImageMagick, with an efficient caching system.
At a glance
- What is it?
- Flyimg is a PHP application in a Docker container that resizes, crops and compresses images on request, producing AVIF, WebP, MozJPEG, PNG or JPEG XL through ImageMagick and serving the result from a cache. It is a self-hosted Cloudinary in one container, and the AGPL-3.0 licence is the first thing to read before you deploy it.
- Who is it for?
- Adopt Flyimg when you want a self-hosted image pipeline with a URL-based option syntax, a cache, and automatic AVIF and WebP output, and you are comfortable running one container that fronts your storage. Do not adopt it as part of a closed-source product without reading the AGPL-3.0 terms first, because the licence is the one thing in this project that cannot be configured away.
- Can I use it commercially?
- Yes, with strict conditions. AGPL-3.0 is a network copyleft licence: if people use a modified version over a network, for example as a hosted service, you must offer them its source code under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One container, one API, no vendor
Flyimg is a Dockerized application that resizes, crops and compresses images on the fly, and the project describes the goal in one line. One Docker container to build your own Cloudinary-like service.
The mechanism is deliberately small. You pass an image URL and a set of options, and Flyimg fetches the image, converts it, stores it, caches it and serves it. The next time the same request comes, it serves the cached version. There is no configuration file describing pipelines, no transform DSL to learn, and no separate upload step, because the source image stays where it is and the URL you request is the whole interface.
The application is PHP on top of ImageMagick, packaged as a container, and the image is built from flyimg/base-image:1.8.2. The repository has a src/ directory for the code, a config/ directory with a parameters.yml file, a web/ directory that holds the front controller and the uploads directory, a tests/ directory and a docs/ directory with mkdocs configuration.
Alongside the open source project there is a managed service offered by the same team, and the readme puts the two side by side. That distinction is worth keeping in mind when you read the rest of the documentation, because the hosted product can have features the container does not.
The URL is the whole API
The calling convention is the part you have to get right, and it is short. You go to the server URL, append /upload/, then pass options, each one an underscore followed by a value, and the options are separated by commas. After the options comes the source of the image, either relative to your server or absolute.
A concrete example from the documentation sets a 250 pixel width and 50 percent compression on a stored image.
<img src="http://imgs.kitty.com/upload/w_250,q_50/https://my.storage.io/imgs/pretty-kitten.jpg">The demo instance shows the same pattern with a width and a quality option.
<img
src="https://demo.flyimg.io/upload/w_300,q_90/https://flyimg.io/demo-images/Citroen-DS.jpg"
/>Two details in the option syntax are worth noticing. The separator is a comma by default but it is configurable, so a pipeline that already uses commas in a parameter can change it. And the source URL is the last segment, which means the cache key is the entire URL including the options, so a different width is a different cache entry rather than a variation on one.
The source is fetched by the server, not by the browser, which is the whole point. The browser never talks to the origin storage, the origin does not need to be public, and the transformation happens once per unique URL.
Format negotiation through the Accept header
By default Flyimg generates the AVIF image format, when the browser supports it, and the documentation claims superior compression compared to other formats. It also generates WebP, applies the MozJPEG compression algorithm, and supports PNG and GIF as well.
The newer part is the explicit negotiation. The documentation says you can use o_auto to negotiate formats based on the request Accept header. That is a meaningful difference from a static configuration, because it means the same URL can return AVIF to one client and a fallback to another, without the caller having to know which format the server will pick.
There is also explicit control for the newest format. JPEG XL output is supported via ImageMagick when it is enabled, and you can force JXL with o_jxl. The encoder effort can be tuned with jxlef_1..9, which is the trade-off knob between encoding time and file size for a codec that is expensive to encode.
The demo URL in the readme shows the forced-format form, with o_jpg alongside the width, height and crop options, so the naming convention is consistent across the whole option set rather than being a separate mechanism.
Caching is what makes the second request cheap
The lifecycle described in the readme is fetch, convert, store, cache, serve, and the last two steps are the reason the service scales the way it does.
Once an image has been transformed, the result is stored and cached, and the next request for the same URL is served from that cache. So the expensive work, which is a network fetch plus an ImageMagick encode, happens once per unique combination of source and options, and every subsequent request is a file read.
That has two consequences that matter operationally. The first is that a hot image is cheap, so the service is mostly a cache with a slow path, and a cold image is expensive, so a crawler or a bot hitting the source URL with random options can cost you real CPU. The second is that the cache is a growing directory that somebody has to think about.
The repository shows the second point being taken seriously. There is a cleanup-tmp.sh script at the top level, the Dockerfile copies it in, marks it executable and runs it once during the build, and the image creates the directories the application writes to, var/tmp and web/uploads/.tmb, with the ownership set to the www-data user.
The .tmb directory name suggests a thumbnail cache, and the tmp directory suggests a working area, so the container treats both as scratch space that is cleaned rather than as permanent storage.
Running the container and changing the parameters
The documented way to run Flyimg is two commands. You pull the image, then you start it with the container's port 80 mapped to 8080 on the host.
docker pull flyimg/flyimgdocker run -itd -p 8080:80 flyimg/flyimgThat gives you the service on port 8080, and the testing instructions say to navigate to the machine's IP on that port, where you should get a message saying Hello from Flyimg and a small homepage.
Configuration works by mounting a file over the one in the image. To use custom parameters, you make a copy of the parameters.yml file from the config directory to your current directory, update it, and run the container with a volume parameter that replaces the original file.
docker run -itd -p 8080:80 -v $(pwd)/parameters.yml:/var/www/html/config/parameters.yml flyimg/flyimgThat is a small but important property of the design. The application is not configured through environment variables that the container reads at start, it is configured through a file that you replace, which means the same image can be run with a minimal configuration and with a heavily tuned one, and a team can keep the tuned version in version control.
The documentation also notes that the project has been tested on Mac, Windows and Ubuntu, and that Docker machine can optionally be used to create a virtual environment.
Building locally, and the s6 supervision inside
The development build is a clone, a build and a run, with one step in between that catches people out.
You clone the repository, change into the folder, and build the image with docker build -t flyimg . The Dockerfile is short. It starts from the base image, copies the whole project into /var/www/html, adjusts the www-data user to UID 1000, creates the var/tmp and web/uploads/.tmb directories with the right ownership, runs composer install with no dev dependencies and an optimised autoloader, and then copies in the cleanup script and runs it.
The step that catches people out is the composer install, and the documentation is explicit about it. If you cloned the project and are running it for the first time, you need to run composer install inside the container, with docker exec -it flyimg composer install. Both this step and the image build are said to take a few minutes, and both are said to be safe to retry if you hit errors.
The final run mounts your working directory over the application directory and names the container, and there is a variant for Fish shell users that uses $PWD instead of $(pwd).
docker run -itd -p 8080:80 -v $(pwd):/var/www/html --name flyimg flyimgThe documentation explains what that command starts. The Dockerfile runs the s6-overlay command, which launches two services, nginx and php-fpm, and the container listens on port 80 while the host port is 8080. So the container is a supervisor with a web server and a PHP process pool, not a single process.
AGPL-3.0 and the managed service question
The licence is AGPL-3.0, and for a service like this it is the single most consequential fact in the repository.
The AGPL is the copyleft licence designed for network software. The obligation it introduces, on top of the obligations of the GPL, is that users who interact with the software over a network are given the corresponding source. For a self-hosted image service, that means the code you deploy and the modifications you make to it are owed to the people who use your instance, which for an internal or hosted service is a different question from the one you get with a permissive licence.
The readme makes no attempt to soften this. It shows an open-source self-hosted Flyimg in one column and a production-ready managed Flyimg SaaS in the other, and it points at the managed service as the way to start in minutes. The two are offered by the same team, which is the arrangement a copyleft project with a commercial offering usually takes, and the reason a company evaluates it is the licence rather than the feature list.
What the licence does not affect is the technical assessment. The service is a single container, the options are in the URL, the cache is on disk, the formats are negotiated, and the build is a single image on a published base. That part is straightforward.
The rest of the repository follows the usual conventions, with a CHANGELOG.md, a CONTRIBUTING.md, a CODE_OF_CONDUCT.md, a docs/ directory built with mkdocs, a phpunit.xml.dist for the test suite, a benchmark.sh script, a python/ directory, an ADOPTERS.md listing organisations that use the project, and Open Collective badges for backers and sponsors. The repository is not archived, the last push was on 2026-09-29, and the releases are frequent on the 1.12 line, with 1.12.6 on 2026-09-29, 1.12.5 on 2026-09-02 and 1.12.4 on 2026-07-27.
Editorial conclusion
Adopt Flyimg when you want a self-hosted image pipeline with a URL-based option syntax, a cache, and automatic AVIF and WebP output, and you are comfortable running one container that fronts your storage. Do not adopt it as part of a closed-source product without reading the AGPL-3.0 terms first, because the licence is the one thing in this project that cannot be configured away. Verify first that your base image can reach the sources you will point it at, since Flyimg fetches the image from the URL you give it, and that the container's writable directories in var/ and web/uploads/ are sized for the cache rather than treated as scratch space.
Frequently asked questions
How do I request a resized image from Flyimg?
Append /upload/ to the server URL, then pass options as underscore and value pairs separated by commas, then the source image URL. For example http://imgs.kitty.com/upload/w_250,q_50/https://my.storage.io/imgs/pretty-kitten.jpg requests a 250 pixel wide image at 50 percent compression.
How do I run Flyimg with Docker?
Run docker pull flyimg/flyimg and then docker run -itd -p 8080:80 flyimg/flyimg. The service then answers on port 8080 on the host, and you should see a Hello from Flyimg message and a small homepage.
Which image formats does Flyimg produce?
AVIF by default when the browser supports it, plus WebP, MozJPEG compression, PNG and GIF, and JPEG XL via ImageMagick when enabled. You can negotiate by the Accept header with o_auto, force JXL with o_jxl, and tune encoder effort with jxlef_1..9.
How do I change the Flyimg configuration?
Copy the parameters.yml file from the config directory to your current directory, edit it, and mount it over the original when starting the container with -v $(pwd)/parameters.yml:/var/www/html/config/parameters.yml.
What does the Flyimg container run inside?
The Dockerfile runs s6-overlay, which launches two services, nginx and php-fpm, and the container listens on port 80 while the host port is 8080. The image is based on flyimg/base-image:1.8.2 and runs composer install with no dev dependencies during the build.
What licence is Flyimg released under?
AGPL-3.0, and the readme offers both the open source self-hosted project and a managed service from the same team. The copyleft terms of the AGPL are the part to check before deploying it inside a closed source product.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/flyimg-flyimg)Was this useful?