BETA VERSION: Functional and production-ready, but may contain issues. Please report bugs or suggestions.
Deploy Docker applications to servers using GitLab/GitHub CI/CD and Ansible. Supports single or multiple containers from one repository.
- Setup your server - Use CLI tool
- Configure CI/CD - Copy example files
- Deploy - Push a git tag
Compatibility:
- GitHub Actions
- GitLab CI
Features:
- Multi-environment deployment (production, staging, etc.)
- Multi-host deployment within same environment
- Multi-container deployment from single repository
- Environment isolation using
${ENV}
variable - SSH keys and secrets management
- Ready-to-use separated CI/CD jobs (build validation + deployment)
- Custom Nginx configurations deployment via templates
- Linux services deployment and management
- Automated script execution on remote servers
Workflow:
- Build Docker images from your Dockerfiles
- Transfer images directly to target servers
- Ansible manages the deployment process
Deployment triggers:
X.Y.Z
→ deploys toproduction
X.Y.Z-[env_name]
→ deploys to specified environment- Trigger on direct branch push → deploys to
production
Prerequisites:
- Remote server: Debian/Ubuntu
- Local machine: Docker Desktop
Automated setup (recommended):
Linux:
docker run -it --rm \
-v ~/.ssh:/root/.ssh \
-v .:/project \
shawiizz/devops-cli:latest
Windows PowerShell:
docker run -it --rm `
-v ${HOME}/.ssh:/root/.ssh `
-v .:/project `
shawiizz/devops-cli:latest
Manual setup: See detailed instructions
1. Copy CI configuration:
- GitHub:
example/ci/github-ci.yml
→.github/workflows/
- GitLab:
example/ci/gitlab-ci.yml
→ repository root
uses
URL in the workflow file if your repository is in an organization.
Available CI/CD jobs:
- build: Validates Docker images build on every push
- deploy: Deploys to target environment when pushing tags
2. Create project structure:
.deployment/
├── docker/
│ ├── docker-compose.yml # Used to define services, volumes, networks, etc...
│ └── Dockerfile.[service_name] # Dockerfile associated to a docker-compose.yml service
└── env/
├── .env.[env_name] # Env file to declare variables
└── .env.[env_name].[host_name] # If using multi-host
3. Add repository secrets:
ANSIBLE_BECOME_PASSWORD
: Ansible user password[ENV_NAME]_SSH_PRIVATE_KEY
: SSH private key for each environment- For multi-host:
[ENV_NAME]_[HOST_NAME]_SSH_PRIVATE_KEY
Notes:
- All secret names must be UPPERCASE
- GitLab secrets must NOT be marked as protected
- The base
.env.[env_name]
file automatically maps to themain
host. DO NOT create additional.env.[env_name].main
file.
Via Git tags:
git tag 1.0.0 # Deploy to production env
git tag 1.0.0-staging # Deploy to staging env
git tag 1.0.0-whatever # Deploy to whatever env
git push origin --tags
Via branch push:
You can also trigger deployments when pushing to a branch directly. This automatically deploys to the production
environment.
The framework will:
- Build your Docker images
- Deploy to specified environment
- Handle multiple services automatically
Create .deployment/env/.env.[env_name]
files with required variables:
HOST=192.168.1.10 # Server IP or CI secret reference
ANSIBLE_USER=ansible # SSH user (usually 'ansible')
# Add any other variables your app needs
You can reference CI secrets to hide sensitive data/credentials:
HOST=$PRODUCTION_HOST # Maps to CI secret 'PRODUCTION_HOST'
ANSIBLE_USER=ansible # SSH user (usually 'ansible')
DB_PASSWORD=$DB_SECRET # Maps to CI secret 'DB_SECRET'
In this way, you can separate variables for each of your environments.
You can also define variables inside the environment
section of your docker compose file instead.
In this case, all of your environments will receive the same variables:
# This is an example
services:
sample-app:
image: ...
...
environment:
DB_PASSWORD: ${DB_SECRET} # DB_SECRET (or whatever you called it) can be defined in .env file or from CI secrets
Deploy to multiple servers in the same environment:
.deployment/env/
├── .env.production # Main host
├── .env.production.a # Host A
├── .env.production.b # Host B
└── .env.production.c # Host C
Host-specific files inherit variables from main file and can override them:
# .env.production
HOST=192.168.1.11
API_PORT=3000 # Example : API Port variable
# .env.production.a
HOST=192.168.1.11
API_PORT=3001 # Override main config
REDIS_URL=redis://host-a:6379 # Exemple : Add host-specific variable
SSH keys for each host:
- Main:
PRODUCTION_SSH_PRIVATE_KEY
- Host A:
PRODUCTION_A_SSH_PRIVATE_KEY
- Host B:
PRODUCTION_B_SSH_PRIVATE_KEY
The .deployment/docker/docker-compose.yml
file can use environment variables.
# This is an example
services:
db:
image: db
build:
context: ../..
dockerfile: Dockerfile.db
container_name: db # Optional, container_name is set as image name without tag by default
restart: always
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # DB_PASSWORD can be defined in .env file or from CI secrets
ports:
- "${DB_EXTERNAL_PORT}:5432" # DB_EXTERNAL_PORT can be defined in .env file or from CI secrets
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- sample-network
app:
image: my-app
build:
context: ../..
dockerfile: Dockerfile.app
container_name: my-app # Optional, container_name is set as image name without tag by default
environment:
ENV: ${ENV} # Pass the current env to your app (if needed)
DB_HOST: my-app # Use db's container name as a network access for the database
DB_PASSWORD: ${DB_PASSWORD} # DB_PASSWORD can be defined in .env file or from CI secrets
ports:
- "${APP_PORT}:3000" # APP_PORT can be defined inside .env file or CI secrets
networks:
- sample-network
volumes:
postgres-data:
networks:
sample-network:
driver: bridge
Environment isolation:
By default, the framework automatically adds ${ENV}
and ${VERSION}
to your image names, container names, volumes, and networks for environment isolation. This is done transparently during deployment.
To use manual environment variables:
Create .deployment/config.yml
:
options:
environmentize: false
Then manually add ${ENV}
and ${VERSION}
where needed for separation:
# This is an example
services:
db:
image: db-${ENV}:${VERSION}
build:
context: ../..
dockerfile: Dockerfile.db
container_name: db-${ENV} # Optional, container_name is set as image name without tag by default
restart: always
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # DB_PASSWORD can be defined in .env file or from CI secrets
ports:
- "${DB_EXTERNAL_PORT}:5432" # DB_EXTERNAL_PORT can be defined in .env file or from CI secrets
volumes:
- postgres-data-${ENV}:/var/lib/postgresql/data
networks:
- sample-network-${ENV}
app:
image: my-app-${ENV}:${VERSION}
build:
context: ../..
dockerfile: Dockerfile.app
container_name: my-app-${ENV} # Optional, container_name is set as image name without tag by default
environment:
ENV: ${ENV} # Pass the current env to your app (if needed)
DB_HOST: my-app-${ENV} # Use db's container name as a network access for the database
DB_PASSWORD: ${DB_PASSWORD} # DB_PASSWORD can be defined in .env file or from CI secrets
ports:
- "${APP_PORT}:3000" # APP_PORT can be defined inside .env file or CI secrets
networks:
- sample-network-${ENV}
volumes:
postgres-data-${ENV}:
networks:
sample-network-${ENV}:
driver: bridge
Create .deployment/config.yml
to customize framework behavior:
options:
environmentize: true # Auto-add ENV/VERSION to names (default: true)
enable_debug_logs: false # Enable detailed deployment logs (default: false)
Options explained:
environmentize
: Whentrue
, automatically adds${ENV}
and${VERSION}
to image names, container names, volumes, and networks for environment isolation. Set tofalse
if you want to manually manage environment variables. (Read the "Compose file" part to learn more about this option)enable_debug_logs
: Whentrue
, enables verbose logging during Ansible deployment for troubleshooting purposes.
Create custom configurations in .deployment/templates/
:
Nginx: nginx/[name].conf.j2
Services: services/[name].service.j2
Scripts: scripts/[name].sh.j2
All templates support Jinja2 syntax and can access environment variables.
Example files: Check example/.deployment/
folder for configuration examples.
Real projects using this framework:
Contributions welcome! See contribution guidelines.
MIT License
Built with ❤️ for the DevOps community