Blog

What's new in Pistacy.io

Is an All-in-One Docker Image Really a Bad Idea?

2025-07-10 by Adam Banaszkiewicz

We can manage, update, restart, and scale each Docker container separately. That's the beauty of containerization. Each container should have a single responsibility. PHP, Nginx, Postgres - each of those does one obvious thing. Creating containers that include multiple services is considered an anti-pattern. The Single Responsibility Principle doesn't apply only to our codebase.

But does it mean that we must not create such anti-pattern containers just because it violates SRP? I believe there is a one good reason why we can violate it, and let me show you that.

The problem

Let me describe the problem I encountered. I built a Software Architecture Platform on top of the Symfony framework. I wanted to have integrated Structurizr inside my system. My platform is delivered as a SaaS, Structurizr is an open-source solution. I wanted to combine both to allow users to have a single place to manage the Software Architecture, and their C4 Model made using the Structurizr DSL.

I needed two pieces to integrate:

  • Structurizr Docker image - to have the ability to render diagrams
  • Structurizr CLI - to validate the DSL

Docker image is a piece of cake. Pull, mount and ready to use. But the Structurizr CLI was a little problematic. I could use a Java container, but the CLI is used in the terminal. Connection between PHP and Java container would have to be done using at least SSH. That would require a lot of configuration in both containers (PHP as a client, Java as a server), and also requires a lot of code inside the application itself, to connect with SSH server and execute the CLI command.

I wondered - is there a faster way to handle that?

Can I install Java directly inside PHP container?

Instead of SSH connection, I could just install Java inside the PHP container. That would allow me to execute the Structurizr CLI just using the shell, in the same container. I could simply use the Symfony Process component as an abstraction. This solution would allow me to save a lot of time!

But there is a catch! Java and PHP are now connected to each other. I cannot scale, update, manage and restart those independently. But, should I care? Let's think about each.

  • Do I need scalability? This is a new SaaS platform, I don't have tens of thousands of users. I don't need to scale yet.
  • Do I need update it separately? I would, but this is not a critical service. It only executes one CLI command. There are no business-critical operations here.
  • Do I need manageability? It's nice to have, but Java is only used for a single CLI command.. No web server, no database connection - just simple local CLI command. I don't need to manage it at large scale. If it fails, the Structurizr integration module will fail as well, and I have logs inside PHP container.
  • Do I need independent restart? Executing this CLI command is strictly connected to the PHP module. Java is treated like a local service, that is available only for that one container, for PHP. When PHP is down, Java is useless. So, no - I don't need such independence.

As you can see, there is no reason to avoid installing Java in PHP. But there is a good reason to install it - the time and cost! I can save a lot of time. Instead of configuring all those things, I can just execute shell command directly. And it works like a charm!

How Does It Work In Real Life?

Pistacy.io uses php:8.3-fpm image as a base for PHP container. The image is built using a Dockerfile, because I need to install custom extensions and libs. Somewhere inside it, there's just one line that installs Java.

FROM php:8.3-fpm AS base

# ...

RUN apt-get install -y default-jre

# ...

In the codebase, I need to execute the CLI command in the shell to validate the Structurizr DSL syntax, and finally transform it into JSON. As mentioned above, Pistacy.io uses the Symfony Process component to do that. The solution looks like the following:

$process = new Process([
    $this->projectDir . '/services/structurizr-cli/structurizr.sh',
    'export',
    '-workspace', $workingDirectory . '/workspace.dsl',
    '-format', 'json',
]);
$process->run();

if (!$process->isSuccessful()) {
    // Parse response error and decide what to do in case of error
}

The structurizr.sh is a official Structurizr CLI release from https://github.com/structurizr/cli. It is installed during deployment and mounted inside the Docker image to be available for the PHP process.

That's all! No additional services, no configuration, no SSH connection. Just old-school shell execution. I saved a lot of time, and it was worth it!

When Is Using an Anti-Pattern a Good Idea?

Remember, such approach is an anti-pattern. In some circumstances, it is worth using:

  • Your 2nd-class service (Java) is dependent on the 1st-class service (PHP). In Pistacy.io, if PHP does not work - Java neither, because there is no one that is able to call it.
  • You don't need to scale, or scallability is dependent for both services at the same level. In terms of Pistacy.io there is no need to scale for now.
  • You don't need to manage/restart/update 2nd-class service. In the Pistacy.io, Java relies on PHP - it does not exist without PHP. Additionally this is only one CLI command executed.
  • Operation executed on 2nd-class service is not business-critical. Validating the Structurizr DSL in Pistacy.io is just one of the features. There are more important features that must work. The priority of that one is low.

But you need to take the potential problems into consideration:

  • Potential failures of 2nd-class service can break the whole container. And this is the most important case in here. Java in PHP container can break whole PHP container - I need to be prepared for it and create a monitoring with some recovery process.
  • Going too far with features that relies on that 2nd-class service without migrating to separate docker container, can lead to significant technical debt. Pistacy.io uses only one CLI command in Java. But if there would be many of them, or Java would be used for a more complex things that just processing CLI commands, it should be moved to separate container.
  • Logs management - 2nd-class service can have own logs directory/files, and you need to cover those to be able to debug it.
  • Increased area of cyberattack. More software = more security gaps.
  • Big image size which can affect the CI/CD pipelines executions time (more data to fetch for environment).

Final thoughts

Such solution works like a charm in my system. But it does not mean that it will work in yours. Remember to always take pros and cons into consideration, to avoid creating a fragile solution, to not create huge Tech Debt.

If you are interested in how this solution works live, I can recommend you to visit https://pistacy.io/, and import the popular Big Bank PLC workspace, which can be found in here: https://github.com/structurizr/examples/blob/main/dsl/big-bank-plc/workspace.dsl. Saving the DSL executes the Structurizr CLI (in Java) to validate it, and executes the Structurizr On-Premises (in Node) to render the diagrams at the background. Everything runs in a single Docker container, using Symfony Process.

If you are looking for the solution for Software Architecture documentation, Pistacy.io allows you to create C4 Models, manage and vote for ADRs and RFCs, create technical documentation, define Architectural Drivers and many more. Try it!

Use Pistacy.io in your project today!

Try Pistacy.io platform - it is free of charge!

It's free - Try it!
We use cookies to ensure that we give you the best experience on our website. You can check what cookies we use in our Privacy Policy.