Docker Compose vs Docker Swarm vs Simplecontainer: A Comprehensive Comparison
Introduction
Container orchestration has become a cornerstone of modern application deployment and management. Whether you're developing a small app locally or managing a multi-node production cluster, choosing the right orchestration tool is critical. While Docker Compose and Docker Swarm have been the default choices for many developers, a fresh new perspective is given for managing containers: Simplecontainer. It offers modern features like native GitOps support, advanced dependency management, and built-in security.
This article provides a comprehensive comparison of these three container orchestration tools, highlighting their features, use cases, and practical deployment examples.
Want to jump directly to the GitOps deployments with Simplecontainer read the tutorial below.

Overview
Docker Compose
Docker Compose is a lightweight tool for defining and running multi-container Docker applications. Using simple YAML files, developers can describe application services, networks, and volumes, then start everything with a single command.
Strengths:
- Excellent for local development and testing.
- Easy setup and low learning curve.
- Simple service dependencies using
depends_on
. - Many repositories contain
docker-compose.yml
making it just plug and play for testing.
Limitations:
- Not designed for production-grade orchestration.
- Lacks built-in high availability, clustering, and load balancing.
- Minimal secrets and configuration management.
Docker Swarm
Docker Swarm is Docker’s native orchestration solution, enabling you to turn multiple Docker hosts into a single, scalable cluster. Swarm mode is integrated directly into the Docker Engine, making it a simpler alternative to Kubernetes for teams already using Docker.
Strengths:
- Supports clustering, service discovery, and scaling.
- Provides high availability and load balancing.
- Works seamlessly with Docker tooling and images.
Limitations:
- Less feature-rich than Kubernetes.
- Limited GitOps support.
- Dependency management and security are basic compared to modern orchestrators.
Simplecontainer
Simplecontainer is a lightweight but powerful orchestrator for Docker containers. It supports both single-node and multi-node clusters and introduces modern DevOps practices such as GitOps, state reconciliation, and built-in security.
Strengths:
- Native GitOps support enables declarative infrastructure workflows.
- Advanced dependency management with readiness probes.
- Secure defaults: mTLS, WireGuard networking, built-in secrets management.
- Seamless transition from development to production.
- Continuous state reconciliation ensures your infrastructure matches the desired configuration.
Limitations:
- Relatively new, so community and ecosystem support are smaller than Docker's.
Feature Comparison
Feature | Docker Compose | Docker Swarm | Simplecontainer |
---|---|---|---|
Deployment Target | Development/Testing | Production | Development & Production |
Clustering | No | Yes | Yes |
High Availability | No | Yes | Yes |
Load Balancing | No | Yes | Built-in |
Service Discovery | Basic | Built-in | Container-aware DNS |
GitOps Support | No | No |
Native GitOps
|
Secrets Management | Basic (env files) | Docker Secrets |
Built-in SSR Objects
|
Configuration Management | Environment variables | Docker Configs |
First-class Configuration
|
Network Security | Basic | Encrypted overlay |
WireGuard by default
|
State Reconciliation | No | Basic |
Continuous reconciliation
|
Dependency Management | depends_on (basic) | None |
Advanced with readiness probes
|
Multi-Node Management | No | Yes |
Single or clustered
|
Learning Curve | Easy | Moderate |
Fast learning curve
|
Dashboard/UI | No | Basic |
Real-time dashboard
|
Template Engine | No | No |
Server-side rendering
|
mTLS Security | No | Limited |
Built-in mTLS
|
Container Replication | No | Yes |
Seamless replication
|
Context Management | No | Limited |
Multiple context support
|
Installation and Setup
Docker Compose
Installation
Installing Docker Compose is done via the installation of the plugin. Docker Compose is now part of the docker
command. Same as Swarm.

DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
Setup
There is no setup needed. After installation, just running the command will work out of the box. The only prerequisite is that the Docker engine has already been installed.
docker compose version
Docker Compose version v2.32.1
Docker Swarm
Installation
The swarm comes as a mode of the Docker engine. So to install Swarm, one needs to install the Docker engine first.

It comes to:
- Choosing your Linux distribution
- Adding Docker repository to the system package manager
And in the end, running:
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
This is also one of the options to install Docker Compose too.
Setup
There are two modes of swarm mode:
- Manager mode
- Worker mode
You need at least one manager node.
To start swarm mode in manager mode:
docker swarm init --advertise-addr <MANAGER-IP>
This will produce a token that can be used by other nodes to join the swarm cluster. Based on the token, it will be distinct whether it is a worker or a manager joining the cluster.
Joining other nodes to the swarm cluster.
docker swarm join --token TOKEN <MANAGER-IP>:2377
Simplecontainer
Installation
The prerequisite for the simplecontainer is that Docker Engine is already installed and not running in the swarm mode. User needs to be non-root with configured access to the /var/run/docker.sock
. This means that docker ps
and other commands will work with a specific non-root user.
Run the command to install needed binaries:
curl -sL https://raw.githubusercontent.com/simplecontainer/smr/refs/heads/main/scripts/production/smrmgr.sh -o smrmgr
chmod +x smrmgr
sudo mv smrmgr /usr/local/bin
sudo smrmgr install
Setup
Starting a single node:
smrmgr start -d node-1.example.com
This will start the node and generate mTLS certificates for node-1.example.com
to enable control-plane access.
Simplecontainer uses contexts for managing control plane access. It is importable/exportable via context commands.
On the smrmgr start
, default context via localhost is imported for the smrctl.
To share control plane access to the other people or the node that wants to join the cluster:
smrctl context export --api node-1.example.com:1443
This will generate an AES-256-encrypted context (string) and key (string) for decryption.
Joining another node to the cluster:
smr agent import ENCRYPTED_CONTEXT KEY
smrmgr start -d node-2.example.com -j
Running this on different machine.
The difference from the Docker Swarm in the setup perspective is the token. Token has the certificates needed for the mTLS against the control plane. Also, the token is already encrypted for secure sharing via any channel.
Another important point is that all nodes are able to talk to the control plane. No matter from which node you export the context, the control plane access will work. All nodes are controllers and workers.
Container Deployment Examples
Docker Compose
services:
backend:
build:
context: backend
target: builder
secrets:
- db-password
depends_on:
db:
condition: service_healthy
db:
# We use a mariadb image which supports both amd64 & arm64 architecture
image: mariadb:10-focal
# If you really want to use MySQL, uncomment the following line
#image: mysql:8
command: '--default-authentication-plugin=mysql_native_password'
restart: always
healthcheck:
test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 --password="$$(cat /run/secrets/db-password)" --silent']
interval: 3s
retries: 5
start_period: 30s
secrets:
- db-password
volumes:
- db-data:/var/lib/mysql
environment:
- MYSQL_DATABASE=example
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password
expose:
- 3306
proxy:
image: nginx
volumes:
- type: bind
source: ./proxy/nginx.conf
target: /etc/nginx/conf.d/default.conf
read_only: true
ports:
- 80:80
depends_on:
- backend
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
https://github.com/docker/awesome-compose/blob/master/nginx-golang-
This Docker Compose file is listed on the awesome-compose repository.
To start the containers using this compose file:
docker-compose up -d
This will start the containers in the background.
Making some changes to the proxy service and reapplying:
docker-compose up -d proxy
Sometimes --force-recreate
is needed.
Docker Swarm
Docker Swarm is an extension to Docker Compose definitions. While a little bit has changed from the docker-compose.yml, the behavior is totally different since Swarm introduces distributed nodes.
If we focus on the example used in Docker Compose, we can deploy it easily.
docker stack deploy -c docker-compose.yml example-compose
This will take the same compose and deploy it to the Swarm cluster.
It will fail because of many factors. It expects the image to already exist in a registry (local images on the manager node aren’t used unless they are tagged and accessible). Also, it is expected ./proxy/nginx.conf
to be present on all nodes. The directive depends_on
will also cause a problem since Swarm doesn't implement dependencies.
Also, this stack needs to be deployed on the manager node, where all files are present, including the secret file for the database.
The problem is also deploying from the CI/CD pipelines. You need to configure remote access to the Docker socket via TLS. That way, the pipeline can access the Docker daemon of the manager node and run commands against Swarm. The process of managing TLS certificates manually inside the pipeline is tedious.
Swarm ready compose.yml
would look like this:
version: "3.9"
services:
backend:
image: nginxdemos/hello:latest
secrets:
- db-password
networks:
- mynet
deploy:
replicas: 1
restart_policy:
condition: on-failure
db:
image: mariadb:10-focal
command: '--default-authentication-plugin=mysql_native_password'
restart: always
healthcheck:
test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 --password="$$(cat /run/secrets/db-password)" --silent']
interval: 3s
retries: 5
start_period: 30s
secrets:
- db-password
volumes:
- db-data:/var/lib/mysql
environment:
- MYSQL_DATABASE=example
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password
expose:
- 3306
networks:
- mynet
deploy:
placement:
constraints:
- node.role == manager
replicas: 1
proxy:
image: nginx
configs:
- source: nginx-config
target: /etc/nginx/conf.d/default.conf
ports:
- 80:80
networks:
- mynet
deploy:
replicas: 2
restart_policy:
condition: on-failure
networks:
mynet:
driver: overlay
volumes:
db-data:
driver: local
configs:
nginx-config:
file: ./proxy/nginx.conf
secrets:
db-password:
file: ./db/password.txt
Docker swarm deploy ready stack.
Nginx conf would look like this:
upstream backend {
server backend:8080;
}
server {
location / {
proxy_pass http://backend;
}
}
Instead of bind mounts, we need to rely on the Docker config and secrets. Command docker secret/config create
is needed or a file directive.
If using file
directive with the remote Docker socket, it will expect that file exist on the manager node.
Now the backend also needs to be restarted a few times before MySQL is ready for connections. Using healthcheck would expect a binary for MySQL connection checking, which is not the best approach.
Upstream will work to reach from the proxy to the backend, no matter the node, without any issues since Swarm enables overlay networking.
Simplecontainer
Simplecontainers approach is totally different. You can define one YAML file or break it into multiple files and create a pack from these files.
A single-file YAML definition would look like this:
---
kind: containers
prefix: simplecontainer.io/v1
meta:
name: myapp
group: backend
labels:
app: backend
tier: backend
spec:
image: "nginxdemos/hello"
tag: "latest"
replicas: 2
dependencies:
- prefix: "simplecontainer.io/v1"
group: "db"
name: "*"
timeout: "60s"
envs:
- DB_HOST=(( .db_host ))
- DB_PASSWORD_FILE=/run/secrets/db-password
ports:
- container: "8080"
resources:
- group: db-password
name: db-password-file
key: "db.password"
mountPoint: /run/secrets/db-password
networks:
- group: internal
name: cluster
configuration:
db_host: "cluster.myapp.db.private"
---
kind: containers
prefix: simplecontainer.io/v1
meta:
name: mariadb
group: db
labels:
app: database
tier: database
spec:
image: "mariadb"
tag: "10-focal"
replicas: 1
command: ["--default-authentication-plugin=mysql_native_password"]
envs:
- MYSQL_DATABASE=example
- MYSQL_ROOT_PASSWORD=(( .db_password ))
ports:
- container: "3306"
volumes:
- type: volume
name: "mysql-data"
mountPoint: /var/lib/mysql
readiness:
- name: "mariadb"
timeout: "60s"
command: ["mysqladmin", "ping", "-h", "127.0.0.1", "--password=(( .db_password ))", "--silent"]
networks:
- group: internal
name: cluster
configuration:
db_password: (( lookup "secret/myapp/db-password:password" | base64decode ))
---
kind: containers
prefix: simplecontainer.io/v1
meta:
name: proxy
group: frontend
labels:
app: nginx
tier: frontend
spec:
image: "nginx"
tag: "latest"
replicas: 1
dependencies:
- prefix: "simplecontainer.io/v1"
group: "backend"
name: "*"
timeout: "30s"
ports:
- container: "80"
host: "80"
networks:
- group: internal
name: cluster
resources:
- group: myapp
name: nginx-config
key: default.conf
mountPoint: /etc/nginx/conf.d/default.conf
---
prefix: simplecontainer.io/v1
kind: resource
meta:
group: myapp
name: nginx-config
spec:
data:
default.conf: |
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://cluster.backend.myapp.private;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
---
prefix: simplecontainer.io/v1
kind: secret
meta:
group: myapp
name: db-password
spec:
data:
password: bXlfc2VjcmV0X3Bhc3N3b3Jk
---
prefix: simplecontainer.io/v1
kind: resource
meta:
group: db-password
name: db-password-file
spec:
data:
db.password: (( lookup "secret/myapp/db-password:password" | base64decode ))
---
prefix: simplecontainer.io/v1
kind: volume
meta:
group: mysql
name: data
spec:
driver: "local"
This defines:
- 3 containers
- 2 resources
- 1 secret
- 1 volume
What do we get with Simplecontainer? Pure declarative definitions are independent of the state of the nodes. No matter the nodes, containers are able to talk securely over an overlay network, which uses WireGuard by default.
You can read more about the internals of overlay networking created by the simplecontainer.

Secrets can be applied together or independently. All nodes can access the secret.
Volume is defined declaratively and applied to all nodes.
Importing context in the pipeline or manually is simple. Afterward, running apply will create all the containers.
smrctl context import $ENCRYPTED_CONTEXT $DECRYPTION_KEY
smrctl apply definition.yaml
This should produce the next screenshots.

After one refresh.

Using Packer for creating a bundle of definitions
Definitions can be broken into multiple files for readability and maintainability. It also allows a client-side templating system that feeds a template with variables from the variables.yaml
smrctl pack init migration
tree migration
migration
├── definitions
│ └── variables.yaml
└── Pack.yaml
2 directories, 2 files
Put all definition files inside the definitions directory.
Running the next command will apply all definitions inside the pack.
smrctl apply migration
Packer is using a Go template to render the template system.
It also supports CLI overrides on the client side.
smrctl apply migration --set env.name="dev"
Another neat feature is that you can acess the logs and exec into containers no matter the node where they are running.
smrctl exec containers/group/name-index --it -c "/bin/bash"
smrctl logs -f containers/group/name-index
Apart from that, for debugging startup issues with the container itself, regarding the orchestration, not the application inside the container itself, a debug command is available.
smrctl debug -f containers/group/name-index
GitOps Deployment with Simplecontainer
One of Simplecontainer's standout features is its native GitOps support. GitOps engine can only deploy packs. Here's how to implement a GitOps workflow for the architecture:
GitOps Configuration
The GitOps definition below assumes:
- The repository is hosted on GitHub
- SSH keys are configured as an authentication method for git operations
prefix: simplecontainer.io/v1
kind: gitops
meta:
group: examples
name: web-app-gitops
spec:
repoURL: "https://github.com/my-org/private-repo"
revision: "main"
automaticSync: true
directoryPath: "/migration"
certKeyRef:
prefix: simplecontainer.io/v1
name: "github-ssh"
group: prod
---
prefix: simplecontainer.io/v1
kind: certkey
meta:
name: github-ssh
group: prod
spec:
privatekey: BASE64_KEY
publickey: BASE64_PUBLIC
After applying this definition to the simplecontainer node, GitOps reconciler will take care of future deployments and reconciliation.
If you’re interested more in detail in GitOps workflows beyond Kubernetes, see our guide on GitOps without Kubernetes.

CI/CD Integration
Simplecontainer integration is simple. Steps to introduce simplecontainer into the pipeline:
smrctl context export --api my.domain.fqdn:1443
ENCRYPTED_CONTEXT DECRYPTION_KEY
- Create a secrets inside pipeline platform (GitHub, Gitlab, Bitbucker, etc.):
ENCRYPTED_CONTEXT
DECRYPTION_KEY
Github example
Plain definitions deploy
In this scenario, the pipeline is deploying the migration pack via apply. This can be useful when the infrastructure directory is in the same repository as service it is deploying. Another scenario could be direct changes pushed to the infrastructure repository.
name: Simplecontainer CI/CD
on:
push:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Setup smrctl
run: |
curl -sL https://raw.githubusercontent.com/simplecontainer/smr/refs/heads/main/scripts/production/smrmgr.sh -o smrmgr
chmod +x smrmgr
sudo mv smrmgr /usr/local/bin
sudo smrmgr install
smrctl context import ${{ secrets.ENCRYPTED_CONTEXT }} ${{ secrets.DECRYPTION_KEY }} -y
smrctl ps
- name: Deploy
run: |
smrctl apply migration
Gitlab example
Gitops update deploy
In this scenario, the Bi-Directional GitOps controller is leveraged. GitOps reconciler can also modify definitions inside the GitOps object and commit them to Git. One useful scenario is when building a service with a new tag, and we need to deploy it, but the infrastructure definitions are in a separate repository.
Instead of hassling with the git clone and kung-fu with credentials, etc. The user can leverage already configured access from the simplecontainer and commit directly.
deploy:
stage: deploy
image: ubuntu
before_script:
- apt-get update && apt-get install -y curl sudo
- curl -sL https://raw.githubusercontent.com/simplecontainer/smr/refs/heads/main/scripts/production/smrmgr.sh -o smrmgr
- chmod +x smrmgr
- sudo mv smrmgr /usr/local/bin
- sudo smrmgr install
- smrctl context import $ENCRYPTED_CONTEXT $DECRYPTION_KEY -y
- smrctl version
script: |
smrctl ps gitops
smrctl commit gitops/examples/web-app-gitops containers/backend/myapp "spec: {image: registry.gitlab.com/backend/myapp, tag: $CI_COMMIT_TAG}"
rules:
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
Key Strengths of Simplecontainer
1. Native GitOps Integration
Unlike Docker Compose and Swarm, Simplecontainer provides built-in GitOps capabilities, enabling true Infrastructure as Code practices with Git as the single source of truth.
2. Advanced Dependency Management
Simplecontainer offers sophisticated dependency management with readiness probes, ensuring containers start in the correct order and only when dependencies are actually ready.
3. Built-in Security
- mTLS encryption by default
- WireGuard-based overlay networking
- Integrated secrets management
- Secure context sharing
4. Unified Development and Production
Simplecontainer works seamlessly from single-node development environments to multi-node production clusters without configuration changes.
5. State Reconciliation
Continuous monitoring and reconciliation ensure the actual state always matches the desired state, providing self-healing capabilities.
6. Comprehensive Resource Management
First-class support for secrets, configurations, and resources as separate, manageable entities, not just environment variables. Any changes to the defintions of secrets, resource or configuration trigger restart of the container.
7. Template Engine
Server-side rendering capabilities allow for dynamic configuration generation and variable substitution.
Client-side rendering of packs to substitute variables and render Go templates.
And many other features. Check the examples repository, docs and main repo.

Use Case Recommendations
Choose Docker Compose when:
- Developing locally
- Simple multi-container applications
- Quick prototyping
- No production orchestration needs
- Team familiar with Docker basics
Choose Docker Swarm when:
- Need simple production orchestration
- Already invested in the Docker ecosystem
- Moderate scaling requirements
- Want built-in Docker integration
- Kubernetes seems too complex
Choose Simplecontainer when:
- Implementing GitOps workflows with self-healing and other neat features
- Need advanced dependency management
- Require built-in security (mTLS, WireGuard)
- Need comprehensive secrets/config management
- Need CI/CD compatible orchestrator
- Desire modern orchestration without Kubernetes complexity
Migration Path
From Docker Compose to Simplecontainer
- Convert docker-compose.yml to simplecontainer definition YAML
- Add secrets and configuration resources
- Implement readiness probes and dependencies
- Deploy with smrctl
smrctl apply definition.yaml
From Docker Swarm to Simplecontainer
- Export current stack configurations
- Convert to Simplecontainer format
- Migrate secrets to Simplecontainer secrets
- Deploy to Simplecontainer cluster
smrctl apply definition.yaml
Conclusion
While Docker Compose remains excellent for development and Docker Swarm provides solid production orchestration, Simplecontainer emerges as a compelling choice for teams seeking modern container orchestration with GitOps capabilities. Its combination of security-first design, advanced dependency management, and seamless scaling from development to production makes it particularly attractive for organizations wanting to modernize their container workflows without the complexity of Kubernetes.
Simplecontainer's native GitOps support, comprehensive resource management, and built-in security features position it as a forward-thinking solution that bridges the gap between simple Docker management and enterprise-grade orchestration. For teams ready to embrace GitOps and declarative infrastructure management while maintaining Docker's simplicity, Simplecontainer represents an innovative and practical choice.
The decision between these tools ultimately depends on your specific requirements, but Simplecontainer's unique features make it worth serious consideration for any team looking to modernize their container orchestration strategy.