Exercises and projects
Objectives
- Consolidate your Portainer knowledge
- Put the learned concepts into practice
- Develop operational skills
Exercise 1: Installation and initial configuration
Objective
Install Portainer and perform the basic configuration.
Tasks
- Install Portainer CE on your local machine
- Create an administrator account
- Explore the dashboard
- Identify the existing resources (containers, images, volumes)
Starting commands
# Créer le volume de données
docker volume create portainer_data
# Déployer Portainer
docker run -d \
-p 8000:8000 \
-p 9443:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
Expected result
- Portainer accessible at https://localhost:9443
- Dashboard displaying the metrics of your environment
Exercise 2: Container management
Objective
Master the basic operations on containers.
Tasks
-
Create an nginx container through the interface
- Name:
web-server - Image:
nginx:alpine - Port:
8080:80
- Name:
-
Access the container logs
-
Open a console and list the files in
/usr/share/nginx/html -
Monitor the resource statistics
-
Create a copy of the container with a different port (8081)
Expected result
- Two working nginx containers
- Ability to debug and monitor
Exercise 3: Deploying a stack
Objective
Deploy a multi-container application.
Tasks
-
Create a new stack named
blog -
Use the following compose:
version: "3.9"
services:
wordpress:
image: wordpress:latest
ports:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress123
WORDPRESS_DB_NAME: wordpress
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress123
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
volumes:
db_data:
-
Deploy the stack
-
Verify that both services are active
-
Access WordPress at http://localhost:8000
Expected result
- Working WordPress stack
- Persistent volume for the database
Exercise 4: Volume management
Objective
Master storage management.
Tasks
-
Create a volume named
app-config -
Use the Browse function to:
- Create a
config.jsonfile - Add configuration content to it
- Create a
-
Create a container that uses this volume:
services:
app:
image: alpine
command: sleep 3600
volumes:
- app-config:/config
volumes:
app-config:
external: true
- Verify that the file is accessible in the container
Expected result
- Volume with a configuration file
- Persistent data between restarts
Exercise 5: Network configuration
Objective
Configure custom networks to isolate services.
Tasks
-
Create two networks:
frontend-net(172.20.0.0/16)backend-net(172.21.0.0/16)
-
Deploy a stack with isolation:
version: "3.9"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend-net
- backend-net
api:
image: nginx:alpine
networks:
- backend-net
database:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
networks:
- backend-net
networks:
frontend-net:
external: true
backend-net:
external: true
- Test the connectivity:
- nginx can reach api and database
- api can reach database
- database cannot reach the outside
Expected result
- Working network isolation
- Services connected according to the architecture
Exercise 6: User management
Objective
Configure access control.
Tasks
-
Create a
developersteam -
Create a
dev1user in this team -
Configure environment access:
developers: Operator (no deletion)
-
Test by logging in as
dev1:- Check the available permissions
- Try to remove a container (must fail)
Expected result
- Working RBAC
- Permissions respected
Project 1: Development environment
Objective
Create a complete development environment managed through Portainer.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Dev Environment │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Traefik │ │ App │ │ Postgres│ │ Redis │ │
│ │ :80 │──▶│ :3000 │──▶│ :5432 │ │ :6379 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │
│ ┌──────▼──────────────▼──────┐ │
│ │ adminer mailhog │ │
│ │ :8080 :8025 │ │
│ └────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Stack
version: "3.9"
services:
traefik:
image: traefik:v2.10
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)"
- "traefik.http.routers.dashboard.service=api@internal"
app:
image: node:18-alpine
command: sh -c "npm install && npm run dev"
working_dir: /app
volumes:
- ./app:/app
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.localhost`)"
- "traefik.http.services.app.loadbalancer.server.port=3000"
depends_on:
- postgres
- redis
postgres:
image: postgres:15
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: app_dev
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
adminer:
image: adminer
labels:
- "traefik.enable=true"
- "traefik.http.routers.adminer.rule=Host(`db.localhost`)"
mailhog:
image: mailhog/mailhog
labels:
- "traefik.enable=true"
- "traefik.http.routers.mailhog.rule=Host(`mail.localhost`)"
- "traefik.http.services.mailhog.loadbalancer.server.port=8025"
volumes:
postgres_data:
Success criteria
- All services accessible via their subdomains
- Working Traefik dashboard
- Database accessible via Adminer
- Emails intercepted by Mailhog
Project 2: Monitoring stack
Objective
Deploy a complete monitoring stack.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Monitoring Stack │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Prometheus│───▶│ Grafana │ │ Alertmgr │ │
│ │ :9090 │ │ :3000 │ │ :9093 │ │
│ └─────┬─────┘ └───────────┘ └───────────┘ │
│ │ │
│ ┌────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │cAdvisor│ │ Node │ │ Pushgw │ │
│ │ :8080 │ │Exporter│ │ :9091 │ │
│ └────────┘ └────────┘ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Stack
version: "3.9"
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-lifecycle'
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
volumes:
- grafana_data:/var/lib/grafana
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
node-exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
alertmanager:
image: prom/alertmanager:latest
ports:
- "9093:9093"
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
volumes:
prometheus_data:
grafana_data:
Success criteria
- Prometheus collects the metrics
- Grafana displays the dashboards
- cAdvisor monitors the containers
- Alertmanager configured
Project 3: Multi-team environment
Objective
Configure Portainer to manage multiple teams with isolation.
Tasks
-
Create 3 teams:
frontend-teambackend-teamdevops-team
-
Create users for each team
-
Create dedicated stacks:
frontend-app(accessible to frontend-team)backend-api(accessible to backend-team)infrastructure(accessible to devops-team)
-
Configure the permissions:
- Each team only accesses its resources
- devops-team has read-only access to everything
Success criteria
- Complete isolation between teams
- devops-team can monitor without modifying
Validation quiz
Questions
-
Which port does Portainer use for HTTPS by default?
- a) 8080
- b) 9000
- c) 9443
- d) 443
-
How do you access a container's console?
- a) Via SSH
- b) Via the Console tab in Portainer
- c) Via Telnet
- d) It is not possible
-
What is the difference between Agent and Edge Agent?
- a) None
- b) Edge Agent initiates the outbound connection
- c) Agent is for Kubernetes
- d) Edge Agent is faster
-
How do you deploy a multi-container application?
- a) Create each container one by one
- b) Use a Stack
- c) Use only the CLI
- d) It is not possible
-
What does the Browse function on a volume allow?
- a) View the logs
- b) Navigate and modify the files
- c) Remove the volume
- d) Mount the volume
Answers
- c) 9443 - Default HTTPS port
- b) Via the Console tab - Exec into the container
- b) Edge Agent initiates the outbound connection - For environments behind NAT
- b) Use a Stack - Docker Compose in Portainer
- b) Navigate and modify the files - Integrated file browser
Additional resources
Official documentation
Community
Conclusion
Congratulations! You have completed the Portainer course.
You now master:
- Installation and configuration
- Managing containers, images, volumes, networks
- Deploying stacks
- User management and RBAC
- Production best practices
Portainer is a powerful tool that considerably simplifies container management. Keep practicing and exploring the advanced features!