Skip to content

Dev to QA Iteration

This guide explains the recommended workflow for iterating between development and QA environments using SNAPSHOT versions.

The dev-to-QA workflow in Odin uses SNAPSHOT versions for fast iteration:

  1. Development: Deploy SNAPSHOT to dev, make changes
  2. QA: Deploy same SNAPSHOT to staging for testing
  3. Iteration: Fix issues, redeploy SNAPSHOT
  4. Release: Create CONCRETE version for production

Create a service with a SNAPSHOT version:

service.json
{
"name": "user-api",
"version": "1.0.0-SNAPSHOT",
"team": "backend",
"components": [
{
"name": "api",
"type": "webservice",
"version": "1.0.0",
"depends_on": ["database"]
},
{
"name": "database",
"type": "postgres",
"version": "14.5"
}
]
}

Deploy to your dev environment:

Terminal window
odin deploy service --env dev \
--file service.json \
--provisioning provisioning-dev.json

provisioning-dev.json (lightweight resources):

[
{
"component_name": "api",
"deployment_type": "kubernetes-deployment",
"params": {
"namespace": "dev",
"cpu": "100m",
"memory": "256Mi",
"replicas": 1,
"image": "myregistry/user-api:dev"
}
},
{
"component_name": "database",
"deployment_type": "rds-postgres",
"params": {
"instance_class": "db.t3.micro",
"storage": "20"
}
}
]

Make changes to your application:

  1. Update code
  2. Build new Docker image
  3. Redeploy the SAME SNAPSHOT version
Terminal window
# Build and push new image
docker build -t myregistry/user-api:dev .
docker push myregistry/user-api:dev
# Redeploy (same version)
odin deploy service --env dev \
--file service.json \
--provisioning provisioning-dev.json

Once development is stable, deploy to QA/staging:

Terminal window
odin deploy service --env staging \
--file service.json \
--provisioning provisioning-staging.json

provisioning-staging.json (more production-like):

[
{
"component_name": "api",
"deployment_type": "kubernetes-deployment",
"params": {
"namespace": "staging",
"cpu": "500m",
"memory": "1Gi",
"replicas": 2,
"image": "myregistry/user-api:staging"
}
},
{
"component_name": "database",
"deployment_type": "rds-postgres",
"params": {
"instance_class": "db.t3.small",
"storage": "50",
"multi_az": false
}
}
]

QA team tests the SNAPSHOT version in staging:

Terminal window
# Check status
odin status env staging --service user-api
# Get service details
odin describe env staging --service user-api

If QA finds issues:

  1. Fix in code
  2. Build new image
  3. Redeploy to dev for verification
  4. Redeploy to staging for QA retest
Terminal window
# Fix code and rebuild
docker build -t myregistry/user-api:dev .
docker push myregistry/user-api:dev
# Test in dev
odin deploy service --env dev \
--file service.json \
--provisioning provisioning-dev.json
# After verification, update staging
docker tag myregistry/user-api:dev myregistry/user-api:staging
docker push myregistry/user-api:staging
odin deploy service --env staging \
--file service.json \
--provisioning provisioning-staging.json

Once QA approves, create a CONCRETE version:

  1. Update version (remove -SNAPSHOT)
  2. Build release image
  3. Tag in Git
service.json
{
"name": "user-api",
"version": "1.0.0", // Removed -SNAPSHOT
"team": "backend",
"components": [...]
}
Terminal window
# Build production image
docker build -t myregistry/user-api:1.0.0 .
docker push myregistry/user-api:1.0.0
# Tag in Git
git tag v1.0.0
git push origin v1.0.0

Deploy the CONCRETE version to production:

provisioning-prod.json (production-grade):

[
{
"component_name": "api",
"deployment_type": "kubernetes-deployment",
"params": {
"namespace": "production",
"cpu": "2",
"memory": "4Gi",
"replicas": 5,
"image": "myregistry/user-api:1.0.0"
}
},
{
"component_name": "database",
"deployment_type": "rds-postgres",
"params": {
"instance_class": "db.r5.large",
"storage": "200",
"multi_az": true,
"backup_retention_days": 14
}
}
]
Terminal window
odin deploy service --env production \
--file service.json \
--provisioning provisioning-prod.json

Begin work on the next version:

{
"name": "user-api",
"version": "1.1.0-SNAPSHOT",
...
}
┌─────────────┐
│ Development │
│ 1.0.0-SNAP │
└──────┬──────┘
│ Deploy
┌─────────────┐
│ Dev │ ←─┐
│ Environment │ │ Iterate
└──────┬──────┘ │
│ Ready │
▼ │
┌─────────────┐ │
│ Staging │ │
│ Environment │ ───┘ Bug Fixes
└──────┬──────┘
│ QA Approved
┌─────────────┐
│ Release │
│ 1.0.0 │
└──────┬──────┘
│ Deploy
┌─────────────┐
│ Production │
│ Environment │
└─────────────┘

Ensure staging reflects production as closely as possible:

Terminal window
# Regular sync from dev to staging
odin deploy service --env staging \
--file service.json \
--provisioning provisioning-staging.json

Tag Docker images consistently:

  • dev: Latest development build
  • staging: Currently in QA
  • 1.0.0: Production releases

Use CI/CD to build images on commits:

.github/workflows/build.yml
name: Build
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build and push
run: |
docker build -t myregistry/user-api:dev .
docker push myregistry/user-api:dev

Track changes in each SNAPSHOT deployment:

Terminal window
# Keep a changelog
echo "$(date): Fixed login bug" >> CHANGELOG-SNAPSHOT.md
git commit -am "Fix login bug"

Always test in dev before passing to staging:

Terminal window
# Dev deployment
odin deploy service --env dev \
--file service.json \
--provisioning provisioning-dev.json
# Manual testing
curl https://dev.api.example.com/health
# If OK, deploy to staging
odin deploy service --env staging \
--file service.json \
--provisioning provisioning-staging.json

Roll back staging to previous working version:

Terminal window
# Option 1: Redeploy previous Docker image
# Update provisioning.json with previous image tag
odin deploy service --env staging \
--file service.json \
--provisioning provisioning-staging.json
# Option 2: Undeploy and redeploy
odin undeploy service user-api --env staging
# Fix issue
odin deploy service --env staging \
--file service.json \
--provisioning provisioning-staging.json

Check provisioning differences:

Terminal window
diff provisioning-dev.json provisioning-staging.json

Ensure resource limits and configurations match:

{
"env_variables": {
"ENVIRONMENT": "staging", // Must match actual environment
"DB_HOST": "staging-db.example.com"
}
}